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

Expansion Cabal Common Library

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Collection of snippets and helpers for OXPs. Collection of snippets and helpers for OXPs.
Identifier oolite.oxp.Svengali.CCL oolite.oxp.Svengali.CCL
Title Cabal Common Library Cabal Common Library
Category Miscellaneous Miscellaneous
Author Cmd.Cheyd, PhantorGorth and Svengali Cmd.Cheyd, PhantorGorth and Svengali
Version 1.7.2 1.7.2
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL http://wiki.alioth.net/index.php/Cabal_Common_Library n/a
Download URL https://wiki.alioth.net/img_auth.php/a/ab/CabalLibray-1.7.2.oxz n/a
License CC BY-NC-SA 3 CC BY-NC-SA 3
File Size n/a
Upload date 1610873407

Documentation

Also read http://wiki.alioth.net/index.php/Cabal%20Common%20Library

Cabal_Common_Library1.7_Readme.rtf

{\rtf1\ansi\ansicpg1252\deff0{\fonttbl{\f0\fnil\fcharset0 Helvetica;}{\f1\fswiss\fprq2\fcharset0 Helvetica;}{\f2\fnil\fcharset238 Helvetica;}}
{\colortbl ;\red128\green128\blue128;\red128\green0\blue128;\red0\green0\blue0;}
{\*\generator Msftedit 5.41.15.1515;}\viewkind4\uc1\pard\lang1031\ul\b\f0\fs24 Cabal_Common_Library1.7\ulnone\b0\fs28  \fs24 for Oolite\f1\par
\cf1\f0\fs18 (C) 2010-2013 by Cmd.Cheyd, PhantorGorth and Svengali\par
License: CC-by.\par
January 2013.\par
\cf0\fs20\par
\ul\fs24 OVERVIEW:\ulnone\fs20\par
\fs22 CCL is a collection of useful snippets and helpers.\par
\par
Its main purpose is to simplify some common tasks used by OXPs and also includes a few\par
unique features. It contains functions to check requirements, helper functions for arrays, strings,\par
numbers and vectors, an encrypt/decrypt algorithm, and functions for missionscreen models,\par
an onscreen keyboard, a comms channel, special market deals, a event driven script for music,\par
a system description based script to coordinate OXPs actions, a tool to check the player ship\par
and NPCs and a tool for inflight overlay handling.\par
\par
In other words it offers new possibilities for OXPs.\par
\par
Over time it will grow and include more functions and features. The library does not alter any native\par
JS objects (like Array or String) to avoid clashes and does not clutter the global namespace.\par
\par
Documentation: \cf2 http://wiki.alioth.net/index.php/Category:OXPDoc\par
\par
\cf3\ul INTERNAL_VERSION:\ulnone  15\cf0\par
\fs20\par
\ul\fs24 REQUIREMENTS:\par
\ulnone\fs22 - Oolite v1.77.\fs20\par
\par
\ul\fs24 PROBLEMS:\par
\ulnone\fs22 In case of problems, please report it: \cf2 http://aegidian.org/bb/viewtopic.php?f=4&t=8839.\par
I\cf0 nclude the following infos:\par
- Oolites version (and if trunk or nightly is used the revision number)\par
- OS, Graphics card (and driver version)\par
- Fullscreen/Windowed mode\par
- Shader mode\par
- List of used OXPs (incl. versions)\fs20\par
\cf3\fs24 _________________________________________________________________________\par
\fs20\par
Thanks to:\par
  - \ul The development team\ulnone\par
  Giles Williams (aegidian), Jens Ayton (Ahruman), Nikos Barkas (another_commander), David Taylor (dajt),\par
  Chris Crowther (hikari), James (cmdrjames), Darren Salt (dsalt), Eddy Petri\f2\'baor\f0  (\f2 eddyp\f0 ), \f2 Erich Ritz\f0  (\f2 eritz\f0 ),\par
  \f2 Konstantinos Sykas\f0  (\f2 getafix\f0 ),  K\f2 aks\f0 , \f2 Nic \f0 (\f2 nic_asdf\f0 ), \f2 Michael Werle\f0  (\f2 mwerle\f0 ), \f2 Dave Hughes\f0  (s\f2 elezen\f0 ),\par
  Eric Walch, \f2 Dylan Smith\f0  (\f2 winston\f0 ).\f2\par
\f0\par
Special thanks:\par
  - Michael Werle for his patience and help to get a much better documentation,\par
  - Ahruman (pseudoRand),\par
  - Commander McLane (strDateFromMinutes),\par
  - Eric Walch (mapCoordsDirection),\par
  - Thargoid (strAddAlignedText, strAddEdgeText, strAddIndentedText, strAdd2Columns and strAdd3Columns)\par
  - Terry Yuen (En/Decryption),\par
  - Dr.J R Stockton (rand, randSpan, msbPos, baseChange, strLZ, strLZZ, arrShuffle),\par
  - Nicholas Zakas (Cabal_Common_BinSearch is heavenly based on his ideas),\par
  - Paul Bourke (pointOnLineB).\f2\par
\f0\fs18\par
\fs24 _________________________________________________________________________\par
\fs20\par
- The content of this OXP is licensed under the Creative Commons Attribution 3.0 Unported License.\par
 To view a copy of this license, visit \cf2 http://creativecommons.org/licenses/by/3.0/\cf3  or send a letter to\par
 Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA.\par
\par
DISCLAIMER:\par
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY\par
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\par
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT\par
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\par
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT\par
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\par
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\par
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\par
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\par
\par
A SMALL PERCENTAGE OF USERS MAY EXPERIENCE EPILEPTIC SEIZURES WHEN EXPOSED TO\par
CERTAIN LIGHT PATTERNS OR BACKGROUNDS ON A COMPUTER SCREEN OR WHILE USING THIS OXP.\par
CERTAIN CONDITIONS MAY INDUCE PREVIOUSLY UNDETECTED EPILEPTIC SYMPTOMS EVEN IN USERS\par
WHO HAVE NO HISTORY OF PRIOR SEIZURES OR EPILEPSY. IF YOU, OR ANYONE IN YOUR FAMILY,\par
HAVE AN EPILEPTIC CONDITION, CONSULT YOUR PHYSICIAN PRIOR TO USING THIS OXP. IMMEDIATELY\par
DISCONTINUE USE OF THIS OXP AND CONSULT YOUR PHYSICIAN IF YOU EXPERIENCE ANY OF THE\par
FOLLOWING SYMPTOMS WHILE USING THIS OXP: DIZZINESS, ALTERED VISION, EYE OR MUSCLE\par
TWITCHES, LOSS OF AWARENESS, DISORIENTATION, ANY INVOLUNTARY MOVEMENT, OR CONVULSIONS.\par
\fs24 _________________________________________________________________________\par
\fs20\par
\par
\par
\par
}

Cabal_Common_Library1.7_Readme.txt

Cabal_Common_Library1.7 for Oolite
(C) 2010-2013 by Cmd.Cheyd, PhantorGorth and Svengali
License: CC-by.
January 2013.

OVERVIEW:
CCL is a collection of useful snippets and helpers.

Its main purpose is to simplify some common tasks used by OXPs and also includes a few
unique features. It contains functions to check requirements, helper functions for arrays, strings,
numbers and vectors, an encrypt/decrypt algorithm, and functions for missionscreen models,
an onscreen keyboard, a comms channel, special market deals, a event driven script for music,
a system description based script to coordinate OXPs actions, a tool to check the player ship
and NPCs and a tool for inflight overlay handling.

In other words it offers new possibilities for OXPs.

Over time it will grow and include more functions and features. The library does not alter any native
JS objects (like Array or String) to avoid clashes and does not clutter the global namespace.

Documentation: http://wiki.alioth.net/index.php/Category:OXPDoc

INTERNAL_VERSION: 15

REQUIREMENTS:
- Oolite v1.77.

PROBLEMS:
In case of problems, please report it: http://aegidian.org/bb/viewtopic.php?f=4&t=8839.
Include the following infos:
- Oolites version (and if trunk or nightly is used the revision number)
- OS, Graphics card (and driver version)
- Fullscreen/Windowed mode
- Shader mode
- List of used OXPs (incl. versions)
_________________________________________________________________________

Thanks to:
  - The development team
  Giles Williams (aegidian), Jens Ayton (Ahruman), Nikos Barkas (another_commander), David Taylor (dajt),
  Chris Crowther (hikari), James (cmdrjames), Darren Salt (dsalt), Eddy Petrişor (eddyp), Erich Ritz (eritz),
  Konstantinos Sykas (getafix),  Kaks, Nic (nic_asdf), Michael Werle (mwerle), Dave Hughes (selezen),
  Eric Walch, Dylan Smith (winston).

Special thanks:
  - Michael Werle for his patience and help to get a much better documentation,
  - Ahruman (pseudoRand),
  - Commander McLane (strDateFromMinutes),
  - Eric Walch (mapCoordsDirection),
  - Thargoid (strAddAlignedText, strAddEdgeText, strAddIndentedText, strAdd2Columns and strAdd3Columns)
  - Terry Yuen (En/Decryption),
  - Dr.J R Stockton (rand, randSpan, msbPos, baseChange, strLZ, strLZZ, arrShuffle),
  - Nicholas Zakas (Cabal_Common_BinSearch is heavenly based on his ideas),
  - Paul Bourke (pointOnLineB).

_________________________________________________________________________

- The content of this OXP is licensed under the Creative Commons Attribution 3.0 Unported License.
 To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/ or send a letter to
 Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA.

DISCLAIMER:
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

A SMALL PERCENTAGE OF USERS MAY EXPERIENCE EPILEPTIC SEIZURES WHEN EXPOSED TO
CERTAIN LIGHT PATTERNS OR BACKGROUNDS ON A COMPUTER SCREEN OR WHILE USING THIS OXP.
CERTAIN CONDITIONS MAY INDUCE PREVIOUSLY UNDETECTED EPILEPTIC SYMPTOMS EVEN IN USERS
WHO HAVE NO HISTORY OF PRIOR SEIZURES OR EPILEPSY. IF YOU, OR ANYONE IN YOUR FAMILY,
HAVE AN EPILEPTIC CONDITION, CONSULT YOUR PHYSICIAN PRIOR TO USING THIS OXP. IMMEDIATELY
DISCONTINUE USE OF THIS OXP AND CONSULT YOUR PHYSICIAN IF YOU EXPERIENCE ANY OF THE
FOLLOWING SYMPTOMS WHILE USING THIS OXP: DIZZINESS, ALTERED VISION, EYE OR MUSCLE
TWITCHES, LOSS OF AWARENESS, DISORIENTATION, ANY INVOLUNTARY MOVEMENT, OR CONVULSIONS.
_________________________________________________________________________




Equipment

Name Visible Cost [deci-credits] Tech-Level
Secure channels no 0 100+
Access Special Markets yes 0 100+
CleanerMine yes 0 100+

Ships

Name
cabal_common_exhaust
cabal_common_key
cabal_common_laser
cabal_common_modelview
CleanerMine
CleanerMineSub
Cargo container
Cargo container

Models

This expansion declares no models.

Scripts

Path
Scripts/Cabal_Common_Briefing.js
"use strict";
this.name = "Cabal_Common_Briefing";
this.author = "Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Helper for Mission - Briefings.";
this.version = "1.7";

this.startUp = function()
{
	delete this.startUp;
	this.helper = new this.Cabal_Common_ScreenFCB();
	this.briefingSoundA = new SoundSource();
	this.briefingSoundB = new SoundSource();
}
// Can be called via worldScripts.Cabal_Common_Briefing.startBriefing(obj);
this.startBriefing = function(obj)
{
	if(!player.ship.docked || !obj) return(false);
	if(!obj.capture){
		if(!obj.title) obj.title = "Briefing";
		if(!obj.background) obj.background = null;
		if(!obj.fadeIn){
			if(!obj.overlay){
				var check = expandMissionText('BGS-IMAGESWITCH');
				if(check==="Yes") obj.overlay = "bgs-i_overlay_none.png";
				else obj.overlay = null;
			}
		} else obj.overlay = {name:"cabal_common_briefing_blend10.png",width:1024,height:480};
		if(!obj.music) obj.music = null;
		this.briefing = obj.briefing;
		if(obj.callbackc) this.briefing.cbc = obj.callbackc;
		else obj.callbackc = null;
		mission.runScreen({title:obj.title,model:obj.role,spinModel:false,background:obj.background,overlay:obj.overlay,music:obj.music,choicesKey:obj.callbackc},this.choiceEval);
		if(obj.cornerPos) this.helper.screenCornerPos(obj.cornerPos[0],obj.cornerPos[1],obj.cornerPos[2],obj.cornerPos[3]);
		if(obj.absolutePos) mission.displayModel.position = [obj.absolutePos[0],obj.absolutePos[1],obj.absolutePos[2]];
		if(obj.ori) mission.displayModel.orientation = obj.ori;
		if(obj.getOri) mission.displayModel.orientation = mission.displayModel.scriptInfo[obj.getOri].split(",");
		if(obj.getPos) mission.displayModel.position = mission.displayModel.scriptInfo[obj.getPos].split(",");
		if(obj.prepareProps){
			var prop;
			for(prop in obj.prepareProps){
				mission.displayModel[prop] = obj.prepareProps[prop];
				if(mission.displayModel.subEntities){
					for(var i=0;i<mission.displayModel.subEntities.length;i++) mission.displayModel.subEntities[i][prop] = obj.prepareProps[prop];
				}
			}
		}
		if(obj.prepareBindings) for(var b=0;b<obj.prepareBindings.length;b++) this.helper.fcbSetBinding(obj.prepareBindings[b][0],obj.prepareBindings[b][1],obj.prepareBindings[b][2],obj.prepareBindings[b][3],obj.prepareBindings[b][4],obj.prepareBindings[b][5],obj.prepareBindings[b][6]);
		if(obj.preparePositions) for(var p=0;p<obj.preparePositions.length;p++) this.helper.rePosition(obj.preparePositions[p][0],obj.preparePositions[p][1],obj.preparePositions[p][2]);
		if(obj.prepareOrientations) for(var o=0;o<obj.prepareOrientations.length;o++) this.helper.reOrient(obj.prepareOrientations[o][0],obj.prepareOrientations[o][1],obj.prepareOrientations[o][2]);
	} else this.helper.fcbRemoveAll();
	if(obj.repRelative){ // relativePosition is meaningless for missionscreens
		var ar = [mission.displayModel],flagM,flag;
		if(mission.displayModel.subEntities && mission.displayModel.subEntities.length){
			for(var i=0;i<mission.displayModel.subEntities.length;i++) ar.push(mission.displayModel.subEntities[i]);
		}
		for(var s=0;s<ar.length;s++){
			flag = 0;
			flagM = 0;
			var a = ar[s].getShaders();
			var b = Object.keys(a);
			if(!b.length){
				a = ar[s].getMaterials();
				b = Object.keys(a);
				flagM = 1;
			}
			if(b.length){
				if(a[b[0]].hasOwnProperty("uniforms")){
					var prop;
					for(prop in a[b[0]].uniforms){
						if(a[b[0]].uniforms[prop]==="relativePosition"){
							a[b[0]].uniforms[prop]="position";
							flag = 1;
						}
					}
				}
				if(flag){
					if(flagM) ar[s].setMaterials(a);
					else ar[s].setShaders(a);
				}
			}
		}
	}
	if(obj.fadeIn) this.briefingFadeIn = obj.fadeIn;
	else this.briefingFadeIn = null;
	if(obj.screenHUD){
		player.ship.hud = obj.screenHUD;
		player.ship.hudHidden = false;
	}
	this.briefingCounter = 0;
	this.briefingIndex = 0;
	this.briefing = obj.briefing;
	if(obj.callback && obj.callbackf){
		this.briefing.cb = obj.callback;
		this.briefing.cbf = obj.callbackf;
	}
	if(obj.delta) this.helper.ccl_defaultDelta = obj.delta;
	else this.helper.ccl_defaultDelta = 0.015;
	if(!this.briefingTimer) this.briefingTimer = new Timer(this,this.doBriefingTimer,0,0.25);
	else this.briefingTimer.start();
	return(true);
}
this.cleanUp = function()
{
	if(this.briefingTimer){
		this.briefingTimer.stop();
		delete this.briefingTimer;
	}
}
this.choiceEval = function(choice){worldScripts.Cabal_Common_Briefing.delayChoice(choice); return;}
this.delayChoice = function(choice)
{
	this.cleanUp();
	this.helper.fcbRemoveAll();
	if(this.briefing.cb && this.briefing.cbf) worldScripts[this.briefing.cb][this.briefing.cbf](choice);
	if(this.briefingSoundA.isPlaying) this.briefingSoundA.stop();
	if(this.briefingSoundB.isPlaying) this.briefingSoundB.stop();
	this.helper.ccl_defaultDelta = 0.015;
	delete this.briefing;
	player.ship.hud = null;
	return;
}
this.doBriefingTimer = function()
{
	if(!this.briefing.length || !mission.displayModel || !mission.displayModel.isValid || this.briefingIndex>=this.briefing.length){
		this.cleanUp();
		this.helper.fcbRemoveAll();
		return;
	}
	if(this.soundStopper && this.briefingCounter >= this.soundStopper[0]){
		if(this.soundStopper[1]) if(this.briefingSoundB.isPlaying) this.briefingSoundB.stop();
		else if(this.briefingSoundA.isPlaying) this.briefingSoundA.stop();
		delete this.soundStopper;
	}
	var current = this.briefing[this.briefingIndex];
	if(this.briefingCounter >= current[0]){
		// Action commands
		switch(current[1]){
			case "bgzoom":
				this.helper.fcbZoomBackground(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6]);
				break;
			case "bind":
				this.helper.fcbSetBinding(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6]);
				break;
			case "check":
				this.helper.fcbCheckAll();
				break;
			case "clean":
				this.helper.fcbRemoveAll();
				break;
			case "continue":
				var checked=false;
				if(this.briefing.cb){
					if(worldScripts[this.briefing.cb][current[2][0]]){
						checked = worldScripts[this.briefing.cb][current[2][0]](current[2][1]);
						if(typeof(checked)==='number'){ // Goto
							this.helper.fcbRemoveAll();
							this.briefingCounter = checked-1;
							this.briefingIndex = checked-1;
						}
					}
				}
				if(!checked && typeof(checked)!=='number'){
					this.cleanUp();
					this.helper.fcbRemoveAll();
					if(this.briefingSoundA.isPlaying && (this.briefingSoundA.repeatCount>1 || this.briefingSoundA.loop)) this.briefingSoundA.stop()
					if(this.briefingSoundB.isPlaying && (this.briefingSoundB.repeatCount>1 || this.briefingSoundB.loop)) this.briefingSoundB.stop()
				}
				break;
			case "face":
				this.helper.fcbFace(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6],current[2][7]);
				break;
			case "flightTo":
				this.helper.fcbFlightTo(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6],current[2][7]);
				break;
			case "revolution":
				this.helper.fcbKIRevolution(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4]);
				break;
			case "kill":
				this.cleanUp();
				this.helper.fcbRemoveAll();
				if(this.briefingSoundA.isPlaying) this.briefingSoundA.stop();
				if(this.briefingSoundB.isPlaying) this.briefingSoundB.stop();
				if(this.briefing.cb && this.briefing.cbf) worldScripts[this.briefing.cb][this.briefing.cbf](current[2]);
				break;
			case "shadeprop":
				this.helper.reShaderProps(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4]);
				break;
			case "mes":
				mission.addMessageText(current[2]);
				break;
			case "mSpeed":
				this.helper.fcbModelSpeed(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6]);
				break;
			case "mVelo":
				this.helper.fcbModelVelo(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6],current[2][7],current[2][8]);
				break;
			case "mVeloTo":
				this.helper.fcbModelVeloTo(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6],current[2][7],current[2][8],current[2][9],current[2][10]);
				break;
			case "prop":
				this.helper.fcbSetProp(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6]);
				break;
			case "reback":
				this.helper.reBackground(current[2][0]);
				break;
			case "reover":
				this.helper.reOverlay(current[2][0]);
				break;
			case "reori":
				this.helper.reOrient(current[2][0],current[2][1],current[2][2]);
				break;
			case "repos":
				this.helper.rePosition(current[2][0],current[2][1],current[2][2]);
				break;
			case "retex":
				this.helper.reTexture(current[2][0],current[2][1],current[2][2],current[2][3]);
				break;
			case "rot":
				this.helper.fcbRotation(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6],current[2][7],current[2][8]);
				break;
			case "stopSound":
				if(this.briefingSoundA.isPlaying) this.briefingSoundA.stop();
				if(this.briefingSoundB.isPlaying) this.briefingSoundB.stop();
				break;
			case "stopVelo":
				this.helper.stopVelo(current[2][0],current[2][1]);
				break;
			case "turn":
				this.helper.fcbFlight(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6]);
				break;
			case "walk":
				this.helper.fcbWalk(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6],current[2][7]);
				break;
			case "zoom":
				this.helper.fcbZoom(current[2][0],current[2][1],current[2][2],current[2][3],current[2][4],current[2][5],current[2][6]);
				break;
		}
		if(current[3]) mission.addMessageText(current[3]);
		if(current[4]){
			if(!this.briefingSoundA.isPlaying){
				this.briefingSoundA.sound = current[4];
				if(current[5]) this.briefingSoundA.repeatCount = current[5];
				else this.briefingSoundA.repeatCount = 0;
				this.briefingSoundA.play();
				if(current[6]){
					if(!current[7]) this.briefingSoundB.stop();
					else this.soundStopper = [current[6],0]
				}
			} else {
				this.briefingSoundB.sound = current[4];
				if(current[5]) this.briefingSoundB.repeatCount = current[5];
				else this.briefingSoundB.repeatCount = 0;
				this.briefingSoundB.play();
				if(current[6]){
					if(!current[7]) this.briefingSoundA.stop();
					else this.soundStopper = [current[6],1]
				}
			}
		}
		this.briefingIndex++;
	}
	if(this.briefingCounter<11 && this.briefingFadeIn){
		var ind = 10-this.briefingCounter;
		var ovn = "cabal_common_briefing_blend"+ind+".png";
		setScreenOverlay({name:ovn,width:1024,height:480});
	}
	this.briefingCounter++;
	return;
}
this.shipWillLaunchFromStation = function()
{
	this.cleanUp();
	if(this.helper.ccl_briefing_fcbs.length) this.helper.fcbRemoveAll();
	if(this.briefingSoundA.isPlaying) this.briefingSoundA.stop();
	if(this.briefingSoundB.isPlaying) this.briefingSoundB.stop();
	delete this.briefing;
}
// API
this.Cabal_Common_ScreenFCB = function(){}
Cabal_Common_ScreenFCB.prototype = {
	constructor: Cabal_Common_ScreenFCB,
	internalVersion: 15,
	ccl_briefing_fcb: 0,
	ccl_briefing_fcbs: [],
	ccl_briefing_bg: 1,
	ccl_defaultDelta: 0.015,
	/* Face target.
		ent			Entity. To be reoriented. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		last		Number. Duration for fcb.
		tar			Entity. Target entity. If -1 player.ship is used.
		basez		Boolean. If true Z position will be taken into account.
		dampa		Number. Linear dampening duration for rotation start.
		dampb		Number. Linear dampening duration for rotation end.
		away		Number. Multiplier to head away from target. -1...1. */
	fcbFace: function(ent,sub,last,tar,basez,dampa,dampb,away){
		var ccl_sum = 0,ccl_ent = ent,ccl_sub = sub,ccl_last = last,ccl_delta=this.ccl_defaultDelta,ccl_tar = tar,ccl_basez = basez,ccl_dampa = dampa,ccl_dampb = dampb,ccl_away = away;
		if(!ent){
			if(ccl_sub===-1) ccl_ent = mission.displayModel;
			else ccl_ent = mission.displayModel.subEntities[ccl_sub];
			if(tar===-1) ccl_tar = player.ship;
			else ccl_tar = mission.displayModel.subEntities[tar];
		} else {
			if(tar===-1) ccl_tar = player.ship;
		}
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_ent || !ccl_tar || !ccl_ent.isValid || !ccl_tar.isValid) return;
			var f=1-(ccl_delta-delta);
			ccl_sum += (delta*f);
			if(ccl_sum>=ccl_last) return;
			var bz = 1,da = 1,db = 1;
			if(ccl_basez) bz = 1-1/Math.sqrt(Math.max(Math.abs(ccl_ent.position.z),0.001));
			if(ccl_dampa && ccl_sum<ccl_dampa) da = 1-((ccl_dampa-ccl_sum)/ccl_dampa);
			if(ccl_dampb && ccl_sum>ccl_last-ccl_dampb) db = (ccl_last-ccl_sum)/ccl_dampb;
			var targetVector = ccl_tar.heading.subtract(ccl_ent.position).direction();
			var angle = (ccl_ent.heading.angleTo(targetVector)*bz*da*db/ccl_last/8)*ccl_away*f;
			var cross = ccl_ent.heading.cross(targetVector).direction();
			ccl_ent.orientation = ccl_ent.orientation.rotate(cross,-angle);
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* Flight.
		ent			Entity. To be reoriented and/or accelerated. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		last		Number. Duration for fcb.
		rU			Number. Rotate vectorUp by rU (radians).
		rR			Number. Rotate vectorRight by rR /radians).
		rF			Number. Rotate vectorForward by rF (radians).
		velo		Number. Multiply vectorForward and set velocity. If -1 no velocity set. */
	fcbFlight: function(ent,sub,last,rU,rR,rF,velo){
		var ccl_sum = 0,ccl_ent = ent,ccl_sub = sub,ccl_last = last,ccl_delta=this.ccl_defaultDelta,ccl_rU = rU,ccl_rR = rR,ccl_rF = rF,ccl_velo = velo;
		if(!ent) ccl_ent = mission.displayModel;
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_ent || !ccl_ent.isValid) return;
			var f=1-(ccl_delta-delta);
			ccl_sum += (delta*f);
			if(ccl_sum>=ccl_last) return;
			if(ccl_sub>-1){
				if(ccl_rU) ccl_ent.subEntities[ccl_sub].orientation = ccl_ent.subEntities[ccl_sub].orientation.rotate(ccl_ent.subEntities[ccl_sub].vectorUp,ccl_rU*f);
				if(ccl_rR) ccl_ent.subEntities[ccl_sub].orientation = ccl_ent.subEntities[ccl_sub].orientation.rotate(ccl_ent.subEntities[ccl_sub].vectorRight,ccl_rR*f);
				if(ccl_rF) ccl_ent.subEntities[ccl_sub].orientation = ccl_ent.subEntities[ccl_sub].orientation.rotate(ccl_ent.subEntities[ccl_sub].vectorForward,ccl_rF*f);
				if(ccl_velo!==-1) ccl_ent.subEntities[ccl_sub].velocity = ccl_ent.subEntities[ccl_sub].vectorForward.multiply(ccl_velo*f);
			} else {
				if(ccl_rU) ccl_ent.orientation = ccl_ent.orientation.rotate(ccl_ent.vectorUp,ccl_rU*f);
				if(ccl_rR) ccl_ent.orientation = ccl_ent.orientation.rotate(ccl_ent.vectorRight,ccl_rR*f);
				if(ccl_rF) ccl_ent.orientation = ccl_ent.orientation.rotate(ccl_ent.vectorForward,ccl_rF*f);
				if(ccl_velo!==-1) ccl_ent.velocity = ccl_ent.vectorForward.multiply(ccl_velo*f);
			}
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* FlightTo.
		ent			mission.displayModel.subentity. To be reoriented and/or accelerated.
		tar			mission.displayModel.subentity. Target.position
		last		Number. Duration for fcb.
		dampa		Number. Linear dampening duration for rotation start.
		dampb		Number. Linear dampening duration for rotation end.
		mul			Number. Multiplier for rotation.
		velo		Number. Multiply vectorForward and set velocity.
		offset		Vector/Array. Offset position for target. */
	fcbFlightTo: function(ent,tar,last,dampa,dampb,mul,velo,offset){
		var ccl_sum = 0,ccl_ent = mission.displayModel.subEntities[ent],ccl_tar = mission.displayModel.subEntities[tar],ccl_last = last,ccl_delta=this.ccl_defaultDelta,ccl_dampa = dampa,ccl_dampb = dampb,ccl_mul = mul,ccl_velo = velo,ccl_offset = new Vector3D(offset);
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_ent || !ccl_tar || !ccl_ent.isValid || !ccl_tar.isValid) return;
			var f=1-(ccl_delta-delta);
			ccl_sum += (delta*f);
			if(ccl_sum>=ccl_last) return;
			var da = 1,db = 1;
			if(ccl_sum<ccl_dampa) da = 1-((ccl_dampa-ccl_sum)/ccl_dampa);
			if(ccl_sum>ccl_last-ccl_dampb) db = (ccl_last-ccl_sum)/ccl_dampb;
			var targetVector = ccl_tar.position.add(ccl_offset).subtract(ccl_ent.position).direction();
			var angle = (ccl_ent.heading.angleTo(targetVector)*da*db/ccl_mul)*f;
			var cross = ccl_ent.heading.cross(targetVector).direction();
			ccl_ent.orientation = ccl_ent.orientation.rotate(cross,-angle);
			if(ccl_velo!==-1){
				var s = ccl_tar.position.add(ccl_offset).distanceTo(ccl_ent.position);
				if(s<ccl_ent.collisionRadius){ccl_sum=9999999; return;}
				var c = ccl_tar.collisionRadius*2;
				var m = 1;
				if(s<c) m = 1/Math.sqrt(c/s);
				ccl_ent.velocity = ccl_ent.vectorForward.multiply(ccl_velo*m*f);
			}
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* Forward KI for revolution joints
		start		Number. Subentity #
		end			Number. Subentity #
		last		Number. Duration for fcb.
		joints		Array. Vectors for connection points
		angles		Array. Max angle (dot product) before reorientation starts
	*/
	fcbKIRevolution: function(start,end,last,joints,angles){
		var ccl_sum=0,ccl_substart=start+1,ccl_subend=end+1,ccl_last=last,ccl_delta=this.ccl_defaultDelta,ccl_joints=joints,ccl_angles=angles;
		var ccl_ent = mission.displayModel,ccl_stepBack = [];
		for(var o=0;o<ccl_subend-ccl_substart;o++) ccl_stepBack.push(0);
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_ent || !ccl_ent.isValid) return;
			var f=1-(ccl_delta-delta);
			ccl_sum += (delta*f);
			if(ccl_sum>=ccl_last) return;
			var vtr;
			for(var i=ccl_substart;i<ccl_subend;i++){
				var offset = new Vector3D(ccl_joints[i-1]),n0 = ccl_ent.subEntities[i-1],n1=ccl_ent.subEntities[i];
				var vt0 = n0.vectorForward,vt1=n1.vectorForward;
				var dt = vt0.dot(vt1);
				if(dt<ccl_angles[i-1] || ccl_stepBack[i]){
					var vt = n0.position.subtract(n1.position).direction();
					var angle = vt1.angleTo(vt)*0.01*f;
					var cross = vt1.cross(vt).direction();
					n1.orientation = n1.orientation.rotate(cross,angle);
					ccl_stepBack[i] = 1;
				}
				if(dt>0.999) ccl_stepBack[i] = 0;
				vtr = offset.rotateBy(n0.orientation);
				n1.position = vtr.add(n0.position);
				if(n0.velocity.magnitude()) n1.velocity=n0.velocity;
			}
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* Pseudospeed.
		ent			Entity. To be accelerated. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		last		Number. Duration for fcb.
		velo		Number. Multiply vectorForward and set velocity.
		basez		Boolean. If true Z position will be taken into account.
		dampa		Number. Linear dampening duration for velo start.
		dampb		Number. Linear dampening duration for velo end. */
	fcbModelSpeed: function(ent,sub,last,velo,basez,dampa,dampb){
		var ccl_sum = 0,ccl_sub = sub,ccl_last = last,ccl_delta=this.ccl_defaultDelta,ccl_ent = ent,ccl_velo = velo,ccl_basez = basez,ccl_dampa = dampa,ccl_dampb = dampb;
		if(!ent) ccl_ent = mission.displayModel;
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_ent || !ccl_ent.isValid) return;
			var f=1-(ccl_delta-delta);
			ccl_sum += (delta*f);
			if(ccl_sum>=ccl_last) return;
			var bz = 1,da = 1,db = 1;
			if(ccl_basez) bz = Math.sqrt(Math.abs(ccl_ent.position.z));
			if(ccl_dampa && ccl_sum<ccl_dampa) da = 1-((ccl_dampa-ccl_sum)/ccl_dampa);
			if(ccl_dampb && ccl_sum>ccl_last-ccl_dampb) db = (ccl_last-ccl_sum)/ccl_dampb;
			if(ccl_sub>-1) ccl_ent.subEntities[sub].velocity = ccl_ent.vectorForward.multiply((ccl_velo*bz*da*db)*f);
			else ccl_ent.velocity = ccl_ent.vectorForward.multiply((ccl_velo*bz*da*db)*f);
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* Velocities.
		ent			Entity. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		last		Number. Duration for fcb.
		mU			Number. Multiplier vectorUp.
		mR			Number. Multiplier vectorRight.
		mF			Number. Multiplier vectorForward.
		basez		Boolean. If true Z position will be taken into account.
		dampa		Number. Linear dampening duration for start.
		dampb		Number. Linear dampening duration for end. */
	fcbModelVelo: function(ent,sub,last,mU,mR,mF,basez,dampa,dampb){
		var ccl_sum = 0,ccl_sub = sub,ccl_last = last,ccl_delta=this.ccl_defaultDelta,ccl_mU = mU,ccl_mR = mR,ccl_mF = mF,ccl_ent = ent,ccl_basez = basez,ccl_dampa = dampa,ccl_dampb = dampb;
		if(!ent) ccl_ent = mission.displayModel;
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_ent || !ccl_ent.isValid) return;
			var f=1-(ccl_delta-delta);
			ccl_sum += (delta*f);
			if(ccl_sum>=ccl_last) return;
			var bz = 1,da = 1,db = 1;
			if(ccl_basez) bz = Math.sqrt(Math.abs(ccl_ent.position.z));
			if(ccl_dampa && ccl_sum<ccl_dampa) da = 1-((ccl_dampa-ccl_sum)/ccl_dampa);
			if(ccl_dampb && ccl_sum>ccl_last-ccl_dampb) db = (ccl_last-ccl_sum)/ccl_dampb;
			if(ccl_sub>-1){
				if(ccl_mU) ccl_ent.subEntities[ccl_sub].velocity = ccl_ent.subEntities[ccl_sub].vectorUp.multiply((ccl_mU*bz*da*db)*f);
				if(ccl_mR) ccl_ent.subEntities[ccl_sub].velocity = ccl_ent.subEntities[ccl_sub].vectorRight.multiply((ccl_mR*bz*da*db)*f);
				if(ccl_mF) ccl_ent.subEntities[ccl_sub].velocity = ccl_ent.subEntities[ccl_sub].vectorForward.multiply((ccl_mF*bz*da*db)*f);
			} else {
				if(ccl_mU) ccl_ent.velocity = ccl_ent.vectorUp.multiply((ccl_mU*bz*da*db)*f);
				if(ccl_mR) ccl_ent.velocity = ccl_ent.vectorRight.multiply((ccl_mR*bz*da*db)*f);
				if(ccl_mF) ccl_ent.velocity = ccl_ent.vectorForward.multiply((ccl_mF*bz*da*db)*f);
			}
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* Velocities.
		ent			Entity. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		last		Number. Duration for fcb.
		mU			Number. Multiplier vectorUp.
		mR			Number. Multiplier vectorRight.
		mF			Number. Multiplier vectorForward.
		basez		Boolean. If true Z position will be taken into account.
		dampa		Number. Linear dampening duration for start.
		dampb		Number. Linear dampening duration for end.
		tar			SubEntity.
		offset		Vector/Array. Offset for target position. */
	fcbModelVeloTo: function(ent,sub,last,mU,mR,mF,basez,dampa,dampb,tar,offset){
		var ccl_sum = 0,ccl_sub = sub,ccl_last = last,ccl_delta=this.ccl_defaultDelta,ccl_mU = mU,ccl_mR = mR,ccl_mF = mF,ccl_ent = ent,ccl_basez = basez,ccl_dampa = dampa,ccl_dampb = dampb,ccl_tar = mission.displayModel.subEntities[tar],ccl_offset=offset;
		if(!ent) ccl_ent = mission.displayModel;
		else ccl_ent = mission.displayModel.subEntity[ent];
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_ent || !ccl_ent.isValid) return;
			var f=1-(ccl_delta-delta);
			ccl_sum += (delta*f);
			if(ccl_sum>=ccl_last) return;
			var bz = 1,da = 1,db = 1;
			if(ccl_basez) bz = Math.sqrt(Math.abs(ccl_ent.position.z));
			if(ccl_dampa && ccl_sum<ccl_dampa) da = 1-((ccl_dampa-ccl_sum)/ccl_dampa);
			if(ccl_dampb && ccl_sum>ccl_last-ccl_dampb) db = (ccl_last-ccl_sum)/ccl_dampb;
			if(ccl_sub>-1){
				if(ccl_mU) ccl_ent.subEntities[ccl_sub].velocity = ccl_ent.subEntities[ccl_sub].vectorUp.multiply((ccl_mU*bz*da*db)*f);
				if(ccl_mR) ccl_ent.subEntities[ccl_sub].velocity = ccl_ent.subEntities[ccl_sub].vectorRight.multiply((ccl_mR*bz*da*db)*f);
				if(ccl_mF) ccl_ent.subEntities[ccl_sub].velocity = ccl_ent.subEntities[ccl_sub].vectorForward.multiply((ccl_mF*bz*da*db)*f);
			} else {
				if(ccl_mU) ccl_ent.velocity = ccl_ent.vectorUp.multiply((ccl_mU*bz*da*db)*f);
				if(ccl_mR) ccl_ent.velocity = ccl_ent.vectorRight.multiply((ccl_mR*bz*da*db)*f);
				if(ccl_mF) ccl_ent.velocity = ccl_ent.vectorForward.multiply((ccl_mF*bz*da*db)*f);
			}
			var s = ccl_tar.position.add(ccl_offset).distanceTo(ccl_ent.subEntities[ccl_sub].position);
			if(s<ccl_ent.subEntities[ccl_sub].collisionRadius*2){ccl_sum=9999999;}
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* Rotation.
		ent			Entity. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		last		Number. Duration for fcb.
		rX			Number. Radians X.
		rY			Number. Radians Y.
		rZ			Number. Radians Z.
		basez		Boolean. If true Z position will be taken into account.
		dampa		Number. Linear dampening duration for start.
		dampb		Number. Linear dampening duration for end. */
	fcbRotation: function(ent,sub,last,rX,rY,rZ,basez,dampa,dampb){
		var ccl_sum = 0,ccl_sub = sub,ccl_last = last,ccl_delta=this.ccl_defaultDelta,ccl_rX = rX,ccl_rY = rY,ccl_rZ = rZ,ccl_ent = ent,ccl_basez = basez,ccl_dampa = dampa,ccl_dampb = dampb;
		if(!ent) ccl_ent = mission.displayModel;
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_ent || !ccl_ent.isValid) return;
			var f=1-(ccl_delta-delta);
			ccl_sum += (delta*f);
			if(ccl_sum>=ccl_last) return;
			var bz = 1,da = 1,db = 1;
			if(ccl_basez) bz = Math.sqrt(Math.abs(ccl_ent.position.z));
			if(ccl_dampa && ccl_sum<ccl_dampa) da = 1-((ccl_dampa-ccl_sum)/ccl_dampa);
			if(ccl_dampb && ccl_sum>ccl_last-ccl_dampb) db = (ccl_last-ccl_sum)/ccl_dampb;
			if(ccl_sub>-1){
				if(ccl_rX) ccl_ent.subEntities[ccl_sub].orientation = ccl_ent.subEntities[ccl_sub].orientation.rotateX((ccl_rX*bz*da*db)*f);
				if(ccl_rY) ccl_ent.subEntities[ccl_sub].orientation = ccl_ent.subEntities[ccl_sub].orientation.rotateY((ccl_rY*bz*da*db)*f);
				if(ccl_rZ) ccl_ent.subEntities[ccl_sub].orientation = ccl_ent.subEntities[ccl_sub].orientation.rotateZ((ccl_rZ*bz*da*db)*f);
			} else {
				if(ccl_rX) ccl_ent.orientation = ccl_ent.orientation.rotateX((ccl_rX*bz*da*db)*f);
				if(ccl_rY) ccl_ent.orientation = ccl_ent.orientation.rotateY((ccl_rY*bz*da*db)*f);
				if(ccl_rZ) ccl_ent.orientation = ccl_ent.orientation.rotateZ((ccl_rZ*bz*da*db)*f);
			}
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* Preserve orientation/position.
		ent			Entity. To be reoriented. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		last		Number. Duration for fcb.
		tar			Entity. Target subEntity.
		dist		Boolean. Keep its distance.
		inheritFE	Boolean. Bind fuel and energy too.
		swap		Boolean. Flip orientation. */
	fcbSetBinding: function(ent,sub,last,tar,dist,inheritFE,swap){
		var ccl_sum = 0,ccl_ent = ent,ccl_sub = sub,ccl_last = last,ccl_tar = tar,ccl_dist = new Vector3D(),ccl_inherit = inheritFE,ccl_swap = swap;
		if(ccl_sub===-1) ccl_ent = mission.displayModel;
		else ccl_ent = mission.displayModel.subEntities[ccl_sub];
		if(tar===-1) ccl_tar = mission.displayModel;
		else ccl_tar = mission.displayModel.subEntities[tar];
		if(swap) ccl_swap = ccl_ent.orientation;
		if(dist) ccl_dist = ccl_tar.position.subtract(ccl_ent.position);
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_ent || !ccl_tar || !ccl_ent.isValid || !ccl_tar.isValid) return;
			ccl_sum += delta;
			if(ccl_sum>=ccl_last) return;
			var p = ccl_tar.position;
			var o = ccl_tar.orientation;
			if(ccl_dist) p = p.subtract(ccl_dist.rotateBy(o));
			if(ccl_swap) o = o.rotate(ccl_tar.vectorUp,-Math.PI);
			ccl_ent.orientation = o;
			ccl_ent.position = p;
			if(ccl_inherit){
				ccl_ent.energy = ccl_tar.energy;
				ccl_ent.fuel = ccl_tar.fuel;
			}
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* Set Property
		ent			Entity. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		prop		String. Property name.
		value		Value for the property. If last specified current value added last times.
		last		Number. Duration for fcb.
		dampa		Number. Linear dampening duration for start.
		dampb		Number. Linear dampening duration for end. */
	fcbSetProp: function(ent,sub,prop,value,last,dampa,dampb){
		var ccl_ent = ent;
		if(!ent) ccl_ent = mission.displayModel;
		if(!last){
			if(sub>-1) ccl_ent.subEntities[sub][prop] = value;
			else ccl_ent[prop] = value;
		} else {
			var ccl_sum = 0,ccl_sub = sub,ccl_last = last,ccl_prop = prop,ccl_value = value,ccl_dampa = dampa,ccl_dampb = dampb,ccl_delta=this.ccl_defaultDelta;
			this.ccl_briefing_fcb = addFrameCallback(function(delta){
				if(!delta || !ccl_ent || !ccl_ent.isValid) return;
				var f=1-(ccl_delta-delta);
				ccl_sum += (delta*f);
				if(ccl_sum>=ccl_last) return;
				var da = 1,db = 1;
				if(ccl_dampa && ccl_sum<ccl_dampa) da = 1-((ccl_dampa-ccl_sum)/ccl_dampa);
				if(ccl_dampb && ccl_sum>ccl_last-ccl_dampb) db = (ccl_last-ccl_sum)/ccl_dampb;
				if(ccl_sub>-1) ccl_ent.subEntities[ccl_sub][ccl_prop] += ccl_value;
				else ccl_ent[ccl_prop] += ccl_value*f;
				return;
			});
			this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
			return;
		}
		return;
	},
	/* Walk.
		ent			Entity. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		last		Number. Duration for fcb.
		steps		Number. Divider for duration.
		movez		Number.
		friction	Number. Modifier for Y movement
		dampa		Number. Linear dampening duration for start.
		dampb		Number. Linear dampening duration for end. */
	fcbWalk: function(ent,sub,last,steps,movez,friction,dampa,dampb){
		var ccl_sum = 0,ccl_ent = ent,ccl_sub = sub,ccl_last = last,ccl_delta=this.ccl_defaultDelta,ccl_dampa = dampa,ccl_dampb = dampb,ccl_fac = last/steps,ccl_movez = movez,ccl_friction = friction;
		if(!ent) ccl_ent = mission.displayModel;
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_ent || !ccl_ent.isValid) return;
			var f=1-(ccl_delta-delta);
			ccl_sum += (delta*f);
			if(ccl_sum>=ccl_last) return;
			var friction = 0,da = 1,db = 1,mf = 1;
			if(ccl_friction){
				friction = Math.cos(ccl_last*ccl_fac*ccl_sum*ccl_friction);
				var abs = Math.abs(friction);
				abs = abs/((1/0.9-2)*(1-abs)+1); // Bias
				friction *= abs*0.065;
			}
			if(ccl_dampa && ccl_sum<ccl_dampa) da = 1-((ccl_dampa-ccl_sum)/ccl_dampa);
			if(ccl_dampb && ccl_sum>ccl_last-ccl_dampb) db = (ccl_last-ccl_sum)/ccl_dampb;
			mf = 1+(da*db);
			if(ccl_sub>-1) ccl_ent.subEntities[ccl_sub].position = ccl_ent.subEntities[ccl_sub].position.subtract([0,friction,ccl_movez*mf*f]);
			else ccl_ent.position = ccl_ent.position.subtract([0,friction,ccl_movez*mf*f]);
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* Zoom.
		ent			Entity. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		last		Number. Duration for fcb.
		amz			Number. Multiplier for Z.
		basez		Boolean. If true Z position will be taken into account.
		dampa		Number. Linear dampening duration for start.
		dampb		Number. Linear dampening duration for end. */
	fcbZoom: function(ent,sub,last,amz,basez,dampa,dampb){
		var ccl_sum = 0,ccl_ent = ent,ccl_sub = sub,ccl_last = last,ccl_delta=this.ccl_defaultDelta,ccl_amz = amz,ccl_basez = basez,ccl_dampa = dampa,ccl_dampb = dampb;
		if(!ent) ccl_ent = mission.displayModel;
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_ent || !ccl_ent.isValid) return;
			var f=1-(ccl_delta-delta);
			ccl_sum += (delta*f);
			if(ccl_sum>=ccl_last) return;
			var bz = 1,da = 1,db = 1,mf = 1;
			if(ccl_basez) bz = 1-1/Math.sqrt(Math.max(Math.abs(ccl_ent.position.z),0.001));
			if(ccl_dampa && ccl_sum<ccl_dampa) da = 1-((ccl_dampa-ccl_sum)/ccl_dampa);
			if(ccl_dampb && ccl_sum>ccl_last-ccl_dampb) db = (ccl_last-ccl_sum)/ccl_dampb;
			if(ccl_amz>1) mf = 1+(da*db*bz*(ccl_amz%1));
			else mf = 1-(f*da*db*bz*(1%ccl_amz));
			if(ccl_sub>-1){
				var pos = ccl_ent.subEntities[ccl_sub].position.multiply(mf);
				ccl_ent.subEntities[ccl_sub].position = pos;
			} else {
				var pos = ccl_ent.position.multiply(mf);
				ccl_ent.position = pos;
			}
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* Zoom background.
		bg			String. Filename with extension
		last		Number. Duration for fcb.
		w			Number. Dimension width.
		h			Number. Dimension height.
		factor		Number. Multiplier Z.
		dampa		Number. Linear dampening duration for start.
		dampb		Number. Linear dampening duration for end. */
	fcbZoomBackground: function(bg,last,w,h,factor,dampa,dampb){
		var ccl_bgf = this.ccl_briefing_bg;
		var ccl_sum = 0,ccl_bg = bg,ccl_delta=this.ccl_defaultDelta,ccl_w = w*ccl_bgf,ccl_h = h*ccl_bgf,ccl_last = last,ccl_factor = factor,ccl_dampa = dampa,ccl_dampb = dampb;
		this.ccl_briefing_bg *= factor;
		this.ccl_briefing_fcb = addFrameCallback(function(delta){
			if(!delta || !ccl_bg) return;
			var f=1-(ccl_delta-delta);
			ccl_sum += (delta*f);
			if(ccl_sum>=ccl_last) return;
			var da = 1,db = 1,mf = 1;
			if(ccl_dampa && ccl_sum<ccl_dampa) da = 1-((ccl_dampa-ccl_sum)/ccl_dampa);
			if(ccl_dampb && ccl_sum>ccl_last-ccl_dampb) db = (ccl_last-ccl_sum)/ccl_dampb;
			if(ccl_factor>1) mf = 1+(da*db*(ccl_factor%1));
			else mf = 1-(f*da*db*(1%ccl_factor));
			setScreenBackground({name:bg,width:ccl_w*mf,height:ccl_h*mf});
			return;
		});
		this.ccl_briefing_fcbs.push(this.ccl_briefing_fcb);
		return;
	},
	/* Check fcbs and log them. */
	fcbCheckAll: function(){
		if(!this.ccl_briefing_fcbs.length) return(false);
		for(var i=0;i<this.ccl_briefing_fcbs.length;i++){
			if(isValidFrameCallback(this.ccl_briefing_fcbs[i])) log("fcbCheck","valid i:"+i+" fcb:"+this.ccl_briefing_fcbs[i]+" typeof:"+typeof(this.ccl_briefing_fcbs[i]));
			else log("fcbCheck","invalid i:"+i);
		}
		return(true);
	},
	/* Clean fcbs. */
	fcbRemoveAll: function(){
		if(!this.ccl_briefing_fcbs.length) return(false);
		for(var i=0;i<this.ccl_briefing_fcbs.length;i++){
			if(isValidFrameCallback(this.ccl_briefing_fcbs[i])) removeFrameCallback(this.ccl_briefing_fcbs[i]);
		}
		this.ccl_briefing_fcb = 0;
		this.ccl_briefing_fcbs = [];
		this.ccl_briefing_bg = 1;
		return(true);
	},
	/* Stop velocity.
		ent			Entity. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub]. */
	stopVelo: function(ent,sub){
		var ccl_ent = ent;
		if(!ent) ccl_ent = mission.displayModel;
		if(sub>-1) ccl_ent.subEntities[sub].velocity = [0,0,0];
		else ccl_ent.velocity = [0,0,0];
		return;
	},
	/* Set background image.
		png			Filename. */
	reBackground: function(png){
		setScreenBackground(png);
		return;
	},
	/* Set overlay image.
		png			Filename. */
	reOverlay: function(png){
		setScreenOverlay(png);
		return;
	},
	/* Set position.
		ent			Entity. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		pos			Vector. */
	rePosition: function(ent,sub,pos){
		var ccl_ent = ent;
		if(!ent) ccl_ent = mission.displayModel;
		if(sub>-1) ccl_ent.subEntities[sub].position = pos;
		else ccl_ent.position = pos;
		return;
	},
	/* Set orientation.
		ent			Entity. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		ori			Quaternion. */
	reOrient: function(ent,sub,ori){
		var ccl_ent = ent;
		if(!ent) ccl_ent = mission.displayModel;
		if(sub>-1) ccl_ent.subEntities[sub].orientation = ori;
		else ccl_ent.orientation = ori;
		return;
	},
	/* Set materials and/or shaders.
		ent			Entity. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		mat			materials object.
		sha			shader object. */
	reTexture: function(ent,sub,mat,sha){
		var ccl_ent = ent;
		if(!ent) ccl_ent = mission.displayModel;
		if(sub>-1) ccl_ent.subEntities[sub].setMaterials(mat,sha);
		else ccl_ent.setMaterials(mat,sha);
		return;
	},
	/* Set Shader properties
		ent			Entity. If not specified mission.displayModel.
		sub			SubEntity. If -1 main entity is used, otherwise ent.subEntities[sub].
		tex			Array. Textures.
		uni			Array. Property names.
		values		Array. Values for the property. */
	reShaderProps: function(ent,sub,tex,uni,values){
		var ccl_ent = ent,a,b,flagM;
		if(!ent) ccl_ent = mission.displayModel;
		if(sub>-1) a = ccl_ent.subEntities[sub].getShaders();
		else a = ccl_ent.getShaders();
		b = Object.keys(a);
		if(!b.length){
			if(sub>-1) a = ccl_ent.subEntities[sub].getMaterials();
			else a = ccl_ent.getMaterials();
			flagM = 1;
			b = Object.keys(a);
		}
		if(!b.length) return;
		if(tex.length) for(var i=0;i<tex.length;i++) a[b[0]].textures=tex;
		if(uni.length) for(var i=0;i<uni.length;i++) a[b[0]].uniforms[uni[i]].value=values[i];
		if(flagM){
			if(sub>-1) ccl_ent.subEntities[sub].setMaterials(a);
			else ccl_ent.setMaterials(a);
		} else {
			if(sub>-1) ccl_ent.subEntities[sub].setShaders(a);
			else ccl_ent.setShaders(a);
		}
		return;
	},
	/* Missionscreen model corner positioning
		level		Number. Works best with values between 0 and 0.35.
		signx		Number. Used as sign mantissa (-1...1) for X axis.
		signy		Number. Used as sign mantissa (-1...1) for Y axis.
		further		Number. Multiplier for Z axis. Defaults to 1.3. */
	screenCornerPos: function(level,signx,signy,further){
		var pos = mission.displayModel.position,w = 1024/oolite.gameSettings.gameWindow.width,h = 768/oolite.gameSettings.gameWindow.height;
		pos.x = level*pos.z*signx;
		pos.y = level*pos.z*0.75*signy;
		if(further) pos.z *= further;
		else pos.z *= 1.3;
		var wh = w/h;
		pos = pos.add([w*wh-1,h*wh-1,0]);
		mission.displayModel.position=pos;
		return;
	}
};
Scripts/Cabal_Common_Comms.js
"use strict";
this.name = "Cabal_Common_Comms";
this.author = "Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Secure comms channels - controlling script.";
this.version = "1.7.2";

this.startUp = function()
{
    delete this.startUp;
    this.helper = new worldScripts.Cabal_Common_Functions.Cabal_Common();
    this.commChannels = [{display:"Discard"},{display:"Open channel"}];
    this.commChannelChanged = true;
    this.patchedIDs = [];
};
/* function: addToComm()
Parameters:
display - String. Used for onscreen display.
who - Entity/String. The entity or worldScript.
ent - Boolean. Must be true for entities, blank for worldScripts.
pID - Number/String. Must be entityPersonality for entities and this.name for worldScripts.
callback - String. The function that handles the actions.
noDist - Boolean. Optional. If true keep entity entry if out of scanner range.
react - Array. Holds the names of the actions.
*/
this.addToComm = function(obj)
{
    if(obj){
        if(!obj.who || !obj.display || !obj.callback || !obj.react || !obj.hasOwnProperty("pID")){
            log(this.name,this.name+" **ERROR: Script has passed invalid settings!");
            var prop;
            for(prop in obj){log(this.name,"Settings: "+prop+" : "+obj[prop]);}
            return(false);
        }
        this.commChannels.push(obj);
        if(obj.pID) this.patchedIDs.push(obj.pID);
        this.commChannelChanged = true;
        this.checkEQ('EQ_CABAL_COMMON_COMM');
        return(true);
    }
    return(false);
};
this.commsMessageReceived = function(message,sender)
{
    if(sender && sender.scriptInfo && sender.scriptInfo.ccl_secureChannel){
        if(this.patchedIDs.indexOf(sender.entityPersonality)===-1){
            var channel = sender.scriptInfo.ccl_secureChannel;
            if(sender.script[channel]) this.checkEQ('EQ_CABAL_COMMON_COMM');
        }
    }
};
/* function: removeFromComm()
Removes object from the list.

> worldScripts.Cabal_Common_Comms.removeFromComm(this.myComm);
*/
this.removeFromComm = function(obj)
{
    if(obj){
        this.patchedIDs = this.helper.arrRemoveByValue(this.patchedIDs,obj.pID);
        this.commChannels = this.helper.arrRemoveByValue(this.commChannels,obj);
        this.commChannelChanged = true;
        return(true);
    }
    return(false);
};
/* function: changeChoicesComm()
Changes the stored actions for a specific entry.
*/
this.changeChoicesComm = function(pID,newSet)
{
    if(this.patchedIDs.indexOf(pID)===-1) return(false);
    var l = this.commChannels.length;
    if(l>2){
        while(l--){
            if(this.commChannels[l].pID===pID){
                this.commChannels[l].react=newSet;
                this.commChannelChanged = true;
                this.checkEQ('EQ_CABAL_COMMON_COMM');
                return(true);
            }
        }
    }
    return(false);
};
this.shipWillEnterWitchspace = function()
{
    this.cleanTimer(); // Avoid eating time
    this.commChannels = [{display:"Discard"},{display:"Open channel"}];
    this.commChannelChanged = false;
    if(player.ship.equipmentStatus('EQ_CABAL_COMMON_COMM')!=='EQUIPMENT_UNAVAILABLE') player.ship.removeEquipment('EQ_CABAL_COMMON_COMM');
};
this.doCleanUpTimer = function()
{
    if(!player.ship.isValid) return;
    var l = this.commChannels.length;
    if(l>2){
        while(l--){
            if(this.commChannels[l].noDist) continue;
            if(this.commChannels[l].ent){
                if(this.commChannels[l].who.isValid && this.commChannels[l].who.position.distanceTo(player.ship.position)<25600) continue;
                else {
                    if(this.commChannels[l].pID) this.patchedIDs = this.helper.arrRemoveByValue(this.patchedIDs,this.commChannels[l].pID);
                    this.commChannels = this.helper.arrRemoveByValue(this.commChannels,this.commChannels[l]);
                    this.commChannelChanged = true;
                    break; // Avoid eating time
                }
            } else if(this.commChannels[l].eID){
                if(this.commChannels[l].eID.isValid && this.commChannels[l].eID.position.distanceTo(player.ship.position)<25600) continue;
                else {
                    if(this.commChannels[l].pID) this.patchedIDs = this.helper.arrRemoveByValue(this.patchedIDs,this.commChannels[l].pID);
                    this.commChannels = this.helper.arrRemoveByValue(this.commChannels,this.commChannels[l]);
                    this.commChannelChanged = true;
                    break; // Avoid eating time
                }
            }
        }
    } else this.cleanTimer(); // Avoid eating time
    return;
};
this.equipmentDamaged = this.equipmentDestroyed = function(equipmentKey)
{
    if(!player.ship.isValid) return;
    if(equipmentKey==='EQ_CABAL_COMMON_COMM') this.checkEQ('EQ_CABAL_COMMON_COMM');
};
this.checkEQ = function()
{
    if(player.ship.equipmentStatus('EQ_CABAL_COMMON_COMM')==='EQUIPMENT_UNAVAILABLE') player.ship.awardEquipment('EQ_CABAL_COMMON_COMM');
    else if(player.ship.equipmentStatus('EQ_CABAL_COMMON_COMM')==='EQUIPMENT_DAMAGED') player.ship.setEquipmentStatus('EQ_CABAL_COMMON_COMM','EQUIPMENT_OK');
    if(!this.cleanUpTimer) this.cleanUpTimer = new Timer(this,this.doCleanUpTimer,0,25);
    else this.cleanUpTimer.start();
    return;
};
this.shipDied = function()
{
    this.cleanTimer();
};
this.cleanTimer = function()
{
    if(this.cleanUpTimer){
        this.cleanUpTimer.stop();
        delete this.cleanUpTimer;
    }
    return;
};
Scripts/Cabal_Common_Functions.js
"use strict";
this.name = "Cabal_Common_Functions";
this.author = "Cmd.Cheyd, PhantorGorth and Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Libraryscript";
this.version = "1.7";

/* class: Cabal_Common
This is the main class for the helper library with its members. Scripts can instantiate a copy, e.g.
> this.myHelper = new worldScripts.Cabal_Common_Functions.Cabal_Common();
*/
this.Cabal_Common = function(){}
Cabal_Common.prototype = {
	constructor: Cabal_Common,
	// Property to give OXPs a chance to check the required lib min. version easily.
	internalVersion: 15,
	// v1.77 spacer
	spacer: String.fromCharCode(31),
	spacerWidth: defaultFont.measureString(String.fromCharCode(31)),
	spacerStandard: String.fromCharCode(32),
	spacerWidthStandard: defaultFont.measureString(String.fromCharCode(32)),
	/* method: baseChange(). Changes the base of n.
	Parameters:
		n - Number. To be changed.
		to - Number. Base which should be used to convert to. Usually 2,10 or 16.
		from - Number. Optional. Base from which should be converted. Usually 2,10 or 16.
	Returns:
		str - String. Base changed.
	Author: Dr.J R Stockton.
	*/
	baseChange: function(n,to,from){
		return(parseInt(n,from || 10).toString(to));
	},
	/* method: clamp()
	Clamps value to max...min.
	Paramaters:
		value - Number. To be clamped.
		max - Number. Maximum.
		min - Number. Minimum.
	Returns:
		n - Number. Clamped.
	*/
	clamp: function(value,max,min){
		return(((value>max)?max:(value<min)?min:value));
	},
	/* method: msbPos()
	Returns position of most significant bit of n.
	Paramaters:
		n - Number. To be evaluated.
	Returns:
		n - Number. The position of the most significant bit or 0 (zero).
	Author: Dr.J R Stockton.
	*/
	msbPos: function(n){
		var e = 15;
		for(var r=8;r>0;r>>=1){
			e = (n<1<<e)?e-r:e+r;
		}
		return((n<1<<e)?e:e+1);
	},
	/* method: nBitsUsed()
	Returns number of bits set in n. If n<0 a bitwise NOT will be applied before execution.
	Parameters:
		n - Number. To be evaluated.
	Returns:
		e - Number. The counted number of bits set to 1.
	*/
	nBitsUsed: function(n){
		var e = 0;
		if(n<0) n = ~n;
		while(n>0){
			if((n&1)) e++;
			n>>=1;
		}
		return(e);
	},
	/* method: num2Prec()
	Returns rounded number with specified precision. Unlike .toFixed() or .toPrecision() trailing zeros are capped.
	Parameters:
		x - Number. To be evaluated.
		p - Number. Decimals.
	Returns:
		e - Number. The rounded number.
	*/
	num2Prec: function(x,p){
		if(!x) return 0;
		var n = Math.pow(10,p);
		return Math.round(x*n)/n;
	},
	/* method: pseudoRand()
	LCG (Linear congruential generator) pseudo-random number generator with salt (and pepper).
	Can be used to generate deterministic unique values, e.g. to decide if a specific entity should be spawned.
	Parameters:
		salt - Number. This is used to generate a pseudo random number.
		mode - Boolean. Optional. If specified and true the returned value is integer (Math.floor(n*100)).
	Returns:
		n - Number. If no mode specified in range [0...1] otherwise [0...100].
	Author: Jens Ayrton (Ahruman).
	*/
	pseudoRand: function(salt,mode){
		var n = system.scrambledPseudoRandomNumber(salt);
		if(mode) n = Math.floor(n*100);
		return(n);
	},
	/* method: rand()
	Random number in range 0...n.
	Parameters:
		n - Number. Maximum value.
	Returns:
		n - Number. Returns the random Number.
	Author: Dr.J R Stockton.
	*/
	rand: function(n){
		return(n*(Math.random()%1) | 0);
	},
	/* method: randSpan()
	Random number in range minN...maxN.
	Paramaters:
		minN - Number. Minimum.
		maxN - Number. Maximum.
	Returns:
		n - Number. The newly generated random Number.
	Author: Dr.J R Stockton.
	*/
	randSpan: function(minN,maxN){
		return minN+this.rand(maxN-minN+1);
	},
	/* method: strAddAlignedText()
	Returns padded text for a single line.
	Parameters:
		text		- String.
		alignment	- String. Optional. Either L, C or R. Default L.
	Returns:
		str			- String. Padded text.
	Author: Thargoid */
	strAddAlignedText: function(text,alignment){
		if(!text) return("");
		var validAlignment = "LCR",textWidth,padCount,padString;
		if(!alignment || validAlignment.indexOf(alignment)===-1) alignment = "L";
		alignment = alignment.toUpperCase();
		textWidth = defaultFont.measureString(text);
		if(textWidth>32 || alignment==="L") return(text);
		padCount = Math.floor((32-textWidth)/this.spacerWidth);
		if(alignment==="C") padCount = Math.floor(padCount/2);
		padString = this.strCreatePadString(padCount);
		return(padString+text);
	},
	/* method: strAddEdgeText()
	Returns two edge-aligned columns, one on the left and the other on the right for a single line.
	Parameters:
		leftText	- String.
		rightText	- String.
	Returns:
		str			- String. Aligned text.
	Author: Thargoid */
	strAddEdgeText: function(leftText,rightText){
		if(!rightText){
			if(leftText) return(leftText);
		}
		var textWidth = defaultFont.measureString(leftText)+defaultFont.measureString(rightText);
		if(textWidth>32){
			log("CCL strAdEdgeText","Text "+leftText+" plus "+rightText+" is too long for a single screen line!");
			return("");
		}
		var padCount = Math.floor((32-textWidth)/this.spacerWidth);
		var padString = this.strCreatePadString(padCount);
		return(leftText+padString+rightText);
	},
	/* method: strAddIndentedText()
	Returns indented line of text by the given distance (in ems) from the left.
	Parameters:
		text		- String.
		indent		- Number.
	Returns:
		str			- String. Indented text.
	Author: Thargoid */
	strAddIndentedText: function(text,indent){
		if(!text) return("");
		var textWidth = defaultFont.measureString(text);
		if(textWidth>32 || typeof(indent)!=='number' || indent<0 || indent>31.8) return(text);
		var padCount = Math.floor(indent/this.spacerWidth);
		var padString = this.strCreatePadString(padCount);
		return(padString+text);
	},
	/* method: strAdd2Columns()
	Returns line of text with two columns indented from the left margin by the given amount (in ems, 0-32).
	Parameters:
		leftText	- String.
		leftIndent	- Number.
		rightText	- String.
		rightIndent	- Number.
	Returns:
		str			- String. Text with 2 columns.
	Author: Thargoid */
	strAdd2Columns: function(leftText,leftIndent,rightText,rightIndent){
		if(!leftText || !rightText || typeof(leftIndent)!=='number' || typeof(rightIndent)!=='number' || leftIndent>rightIndent || leftIndent<0 || leftIndent>31.8 || rightIndent<0 || rightIndent>31.8) return("");
		var textWidth = defaultFont.measureString(leftText)+defaultFont.measureString(rightText);
		if(textWidth>32){
			log("CCL strAdd2Columns","2 column text is too long for a single screen line!");
			return("");
		}
		var leftPad = this.strCreatePadString(Math.floor(leftIndent/this.spacerWidth));
		var rightCount = rightIndent-(leftIndent+defaultFont.measureString(rightText));
		var rightPad;
		if(rightCount<=0){
			log("CCL strAdd2Columns","Left column overrun!");
			rightPad = "";
		} else rightPad = this.strCreatePadString(Math.floor(rightCount/this.spacerWidth));
		return(leftPad+leftText+rightPad+rightText);
	},
	/* method: strAdd3Columns()
	Returns text with three columns, left, centre and right.
	Parameters:
		leftText	- String.
		leftIndent	- Number.
		centreText	- String.
		centreIndent- Number.
		rightText	- String.
		rightIndent	- Number.
	Returns:
		str			- String. Text with 3 columns.
	Author: Thargoid */
	strAdd3Columns: function(leftText,leftIndent,centreText,centreIndent,rightText,rightIndent){
		if(!leftText || !centreText || !rightText || typeof(leftIndent)!=='number' || typeof(centreIndent)!=='number' || typeof(rightIndent)!=='number' || leftIndent>centreIndent || centreIndent>rightIndent || leftIndent<0 || leftIndent>32 || centreIndent<0 || centreIndent>32 || rightIndent<0 || rightIndent>32) return("");
		var textWidth = defaultFont.measureString(leftText)+defaultFont.measureString(centreText)+defaultFont.measureString(rightText);
		if(textWidth>32){
			log("CCL atrAdd3Columns","3 column text is too long for a single screen line!");
			return("");
		}
		var leftPad = this.strCreatePadString(Math.floor(leftIndent/this.spacerWidth));
		var centreCount = centreIndent-(leftIndent+defaultFont.measureString(centreText));
		var centrePad,rightPad;
		if(centreCount<=0){
			log("CCL strAdd3Columns","Left column overrun!");
			centrePad = "";
		} else centrePad = this.strCreatePadString(Math.floor(centreCount/this.spacerWidth));
		var rightCount = rightIndent-(centreIndent+defaultFont.measureString(rightText));
		if(rightCount<=0){
			log("CCL strAdd3Columns","Centre column overrun!");
			rightPad = "";
		} else rightPad = this.strCreatePadString(Math.floor(rightCount/this.spacerWidth));
		return(leftPad+leftText+centrePad+centreText+rightPad+rightText);
	},
	/* method: strCompareVersion()
	Compares version strings (like "2.0.3") against each other and returns number.
	It also strips pre/suffixes like "Build" or "Beta".
	Parameters:
		strA - String. Actual version.
		strB - String. Version to be compared against.
	Returns:
		0 - Number. If strA < strB
		1 - Number. If strA = strB
		2 - Number. If strA > strB
	*/
	strCompareVersion: function(strA,strB){
		var x = strA.split('.'),y = strB.split('.');
		if(isNaN(parseInt(x[0],null))){
			var n = x[0].search(/\d+/);
			if(n!==-1) x[0] = parseFloat(x[0].substr(n),null);
			else x.shift();
		}
		var xl = x.length,yl = y.length;
		if(!xl) return(0);
		for(var i=0;i<yl;i++){
			if(xl<=i) return(0);
			if(parseInt(x[i],null)>y[i]) return(2);
			if(parseInt(x[i],null)<y[i]) return(0);
		}
		return(1);
	},
	/* Internal method
	Author: Thargoid */
	strCreatePadString: function(count){
		var padString = "";
		if(!count || count<=0) return(padString);
		var counter = 0;
		for(counter=0;counter<count;counter++){
			padString = padString+this.spacer;
		}
		return(padString);
	},
	/* method: strEncrypt()
	Encrypts string.
	Parameters:
		str - String. To be encrypted. Minimum length 6 chars.
		pwd - String. Password. Minimum length 4 chars.
	Returns:
		enc_str - String/Boolean. Encrypted string or false.
	Author: Terry Yuen.
	*/
	strEncrypt: function(str,pwd){
		if(!str || !pwd || str.length<6 || pwd.length<4){log("Cabal_Common","Parameters error in encrypt."); return(false);}
		var prand = "";
		for(var i=0;i<pwd.length;i++) prand += pwd.charCodeAt(i).toString();
		var sPos = Math.floor(prand.length/5);
		var mult = parseInt(prand.charAt(sPos)+prand.charAt(sPos*2)+prand.charAt(sPos*3)+prand.charAt(sPos*4)+prand.charAt(sPos*5),null);
		var incr = Math.ceil(pwd.length/2);
		var modu = Math.pow(2,31)-1;
		if(mult<2){log("Cabal_Common","Algorithm cannot find a suitable hash."); return(false);}
		var salt = Math.round(Math.random()*1000000000)%100000000;
		prand += salt;
		while(prand.length>10) prand = (parseInt(prand.substring(0,10),null)+parseInt(prand.substring(10,prand.length),null)).toString();
		prand = (mult*prand+incr)%modu;
		var enc_chr = "",enc_str = "";
		for(var i=0;i<str.length;i++){
			enc_chr = parseInt(str.charCodeAt(i)^Math.floor((prand/modu)*255),null);
			if(enc_chr<16) enc_str += "0"+enc_chr.toString(16);
			else enc_str += enc_chr.toString(16);
			prand = (mult*prand+incr)%modu;
		}
		salt = salt.toString(16);
		while(salt.length<8) salt = "0"+salt;
		enc_str += salt;
		return(enc_str);
	},
	/* method: strDecrypt()
	Decrypts string.
	Parameters:
		str - String. To be decrypted. Minimum length 6 chars.
		pwd - String. Password. Minimum length 4 chars.
	Returns:
		dec_str - String/Boolean. Decrypted string or false.
	Author: Terry Yuen.
	*/
	strDecrypt: function(str,pwd){
		if(!str || !pwd || str.length<6 || pwd.length<4){log("Cabal_Common","Parameters error."); return(false);}
		var prand = "";
		for(var i=0;i<pwd.length;i++) prand += pwd.charCodeAt(i).toString();
		var sPos = Math.floor(prand.length/5);
		var mult = parseInt(prand.charAt(sPos)+prand.charAt(sPos*2)+prand.charAt(sPos*3)+prand.charAt(sPos*4)+prand.charAt(sPos*5),null);
		var incr = Math.round(pwd.length/2);
		var modu = Math.pow(2,31)-1;
		var salt = parseInt(str.substring(str.length-8,str.length),16);
		str = str.substring(0,str.length-8);
		prand += salt;
		while(prand.length>10) prand = (parseInt(prand.substring(0,10),null)+parseInt(prand.substring(10,prand.length),null)).toString();
		prand = (mult*prand+incr)%modu;
		var enc_chr = "",dec_str = "";
		for(var i=0;i<str.length;i+=2){
			enc_chr = parseInt(parseInt(str.substring(i,i+2),16)^Math.floor((prand/modu)*255),null);
			dec_str += String.fromCharCode(enc_chr);
			prand = (mult*prand+incr)%modu;
		}
		return(dec_str);
	},
	/* method: strGetCRC()
	Simple CRC from string. Does not create unique checksums. It uses Unicodes, but limits every char via &0xff and the output via &0x3fff.
	Parameters:
		str - String. Which should be used to create a CRC.
	Returns:
		crc - Number. The CRC.
	*/
	strGetCRC: function(str){
		var crc = 0, i = str.length;
		while(i--) crc += (str.charCodeAt(i)&0xff);
		return((crc&0x3fff));
	},
	/* method: strDateFromMinutes()
	Takes an integer number of minutes and converts it into a string.
	The returned string has the form 'd' days, 'h' hours and 'm' minutes, accomodating all cases of singular, plural, and 0.
	Parameters:
		minutes - Number. The time in minutes.
	Returns:
		timeString - String.
	Author: Commander McLane.
	*/
	strDateFromMinutes: function(minutes){
		var ago = false;
		if(minutes<0) ago = true;
		minutes = Math.abs(minutes);
		var daysComponent = Math.floor(minutes/24/60);
		var hoursComponent = Math.floor((minutes%(24*60))/60);
		var minutesComponent = minutes%60;
		var timeString = "";
		if(daysComponent>0) timeString += daysComponent + " day";
		if(daysComponent>1) timeString += "s";
		if(timeString!==""){
			if(hoursComponent>0 && minutesComponent>0) timeString += ", ";
			else if(hoursComponent>0) timeString += " and ";
		}
		if(hoursComponent>0) timeString += hoursComponent + " hour";
		if(hoursComponent>1) timeString += "s";
		if(timeString!=="" && minutesComponent>0) timeString += " and ";
		if(minutesComponent>0) timeString += minutesComponent + " minute";
		if(minutesComponent>1) timeString += "s";
		if(timeString==="") timeString = "no time";
		if(ago) timeString += " ago";
		return timeString;
	},
	/* method: strDateRelative()
	Returns string (e.g. '4 minutes ago').
	Parameters:
		inSec - Number. In seconds.
	Returns:
		str - String.
	*/
	strDateRelative: function(inSec){
		var reference = clock.absoluteSeconds,delta,formats,format,i,len;
		var sec = 1; var min = 60*sec; var hour = 60*min; var day = 24*hour; var week = 7*day; var year = day*365; var month = year/12;
		if(inSec<=0){
			delta = reference-(clock.absoluteSeconds+inSec);
			formats = [[0.7*min,'just now'],[1.5*min,'a minute ago'],[60*min,'minutes ago',min],[1.5*hour,'an hour ago'],
				[0.7*day,'hours ago',hour],[day,'today'],[2*day,'yesterday'],[7*day,'days ago',day],[1.5*week,'a week ago'],
				[month,'weeks ago',week],[1.5*month,'a month ago'],[year,'months ago',month],[1.5*year,'a year ago'],
				[Number.MAX_VALUE,'years ago',year]];
		} else {
			delta = (clock.absoluteSeconds+inSec)-reference;
			formats = [[0.7*min,'just now'],[1.5*min,'a minute'],[60*min,'minutes',min],[1.5*hour,'an hour'],
				[0.7*day,'hours',hour],[day,'today'],[2*day,'tomorrow'],[7*day,'days',day],[1.5*week,'a week'],
				[month,'weeks',week],[1.5*month,'a month'],[year,'months',month],[1.5*year,'a year'],
				[Number.MAX_VALUE,'years',year]];
		}
		for(i=-1,len=formats.length;++i<len;){
			format = formats[i];
			if(delta<format[0]) return format[2] == undefined?format[1]:Math.round(delta/format[2])+' '+format[1];
		}
	},
	/* method: strLZ()
	Returns string with a leading zero if n<10.
	Parameters:
		n - Number. To be evaluated.
	Returns:
		str - String. With leading zero if necessary.
	Author: Dr.J R Stockton.
	*/
	strLZ: function(n){
		return (n!=null&&n<10&&n>=0?"0":"")+n;
	},
	/* method: strLZZ()
	Returns string with a leading zeros if n<100. Uses <strLZ()> as subfunction.
	Parameters:
		n - Number. To be evaluated.
	Returns:
		str - String. With leading zeros if necessary.
	Author: Dr.J R Stockton.
	*/
	strLZZ: function(n){
		return(n!=null&&n<100&&n>=0?"0"+this.strLZ(n):""+n);
	},
	/* method: strNLZ()
	Returns string with length of len filled with leading zeros.
	Parameters:
		n - Number. To be evaluated.
		len - Number. Length. Maximum is 10.
	Returns:
		str - String. Filled up with leading zeros.
	*/
	strNLZ: function(n,len){
		var s = n.toString();
		if(!len || len<s.length) len = s.length;
		if(len && len>10) len = 10;
		if(s.length<len) s = ("0000000000"+s).slice(-len);
		return s;
	},
	/* method: strRandom()
	Returns random string with length of l.
	Parameters:
		l - Number. Length.
	Returns:
		str - String.
	*/
	strRandom: function(l){
		var str = "";
		for(var j=0;j<l;j++) str += String.fromCharCode(97+this.rand(26));
		return str;
	},
	/* method: strRandomInt()
	Returns random numbered string with length of l.
	Parameters:
		l - Number. Length in range of 1...9.
	Returns:
		str - String.
	*/
	strRandomInt: function(l){
		var str = String(1000000000+Math.random()*1000000000 | 0);
		return str.substr(0,l);
	},
	/* method: strScreenSubString()
	Formats the string str into lines of at most chrs characters in length.
	Parameters:
		str - String. To be used.
		chrs - Number. The length.
	Returns:
		out - String. With specified length.
	*/
	strScreenSubString: function(str,chrs){
		var out = "";
		for(var d=0;d<(str.length/chrs);d++) out += str.substr(d*chrs,chrs)+"\n";
		return(out);
	},
	/* method: strScreenString()
	Splits string in pieces of chrs, with intermediate linebreaks for missionscreens and fills up current line.
	If splitting is not possible or a word is longer than chrs, <strScreenSubString()> will be used.
	Parameters:
		str - String. To be used.
		mode - Boolean. Optional. If mode is false or not specified it replaces special signs like linebreaks with spaces.
		chrs - Number. Optional. Length. If not specified default of 70 is used.
	Returns:
		display - String. With linebreaks.
	*/
	strScreenString: function(str,mode,chrs){
		if(!mode) str = str.replace(/[\t\r\f\n\v]/g," ");
		if(!chrs) chrs = 70;
		var display = "", splob = str.split(" ");
		var l = splob.length, temp = "";
		if(l<2) display = this.strScreenSubString(str,chrs);
		else {
			for(var o=0;o<l;o++){
				if(splob[o].length>chrs){
					display += temp+"\n";
					display += this.strScreenSubString(splob[o],chrs);
					temp = "";
					continue;
				}
				if(!o && splob[o].length<chrs){
					temp = splob[o];
					continue;
				}
				if((temp.length+splob[o].length)<chrs){
					if(temp!=="") temp += " "+splob[o];
					else temp += splob[o];
				} else {
					display += temp+"\n";
					temp = splob[o];
				}
			}
			if(temp!=="") display += temp;
		}
		return(display);
	},
	/* method: strSplitToPhrases()
	Splits string into n words phrases.
	Parameters:
		str - String. To be used.
		n - Number. Words.
	Returns:
		out - Array. Containing Strings with n words per element.
	*/
	strSplitToPhrases: function(str,n){
		var arr = str.split(" ");
		var l = arr.length, out = [], sub = "";
		for(var o=0;o<l;o++){
			if(!arr[o] || arr[o]==="") continue;
			if(o>l-n) break;
			sub = "";
			for(var i=o;i<Math.min(o+n,l);i++){
				if(!arr[i] || arr[i]=="") continue;
				sub += arr[i]+" ";
			}
			sub = sub.substr(0,sub.length-1);
			out.push(sub);
		}
		return(out);
	},
	/* method: strToWidth()
	Returns string with specific length based on Oolites font size.
	If stringwidth > max it is truncated, otherwise filled up with space or specified chr.
	Parameters:
		str - String. To be used.
		max - Number. Maximum width.
		chr - String. Char to be used to fill up if stringwidth < max.
	Returns:
		str - String. With specified width.
	*/
	strToWidth: function(str,max,chr){
		var l = defaultFont.measureString(str),c,d;
		if(chr) c = defaultFont.measureString(chr);
		if(l>max){
			while(l>max){
				str = str.substr(0,str.length-1);
				d = defaultFont.measureString(str[str.length-1]);
				l -= d;
			}
		} else {
			while(l<max){
				if(chr && chr!==" "){
					str += chr;
					l += c;
				} else {
					if(max-l>1.7){
						str += this.spacerStandard;
						l += this.spacerWidthStandard;
					} else {
						if(max-l>0.1){
							str += this.spacer;
							l += this.spacerWidth;
						} else break;
					}
				}
			}
		}
		return(str);
	},
	/* method: strTrim()
	Returns trimmed string.
	Parameters:
		str  - String.
	Returns:
		str - String.
	*/
	strTrim: function(str){
		return(str.replace(/[\b\f\r\n]/g,"").toString().replace(/\s+$|^\s+/g,""));
	},
	/* method: arrDiff()
	Returns difference between two arrays.
	Parameters:
		ar1 - Array. First.
		ar2 - Array. Second.
	Returns:
		diff - Array. Difference between two arrays.
	*/
	arrDiff: function(ar1,ar2){
		var a=[], diff=[];
		for(var i=0;i<ar1.length;i++) a[ar1[i]]=true;
		for(var i=0;i<ar2.length;i++){
			if(a[ar2[i]]) delete a[ar2[i]];
			else a[ar2[i]]=true;
		}
		return diff;
	},
	/* method: arrRemoveByValue()
	Removes specific entry from array.
	Parameters:
		arr - Array. From which things should be removed.
		val - Object. Value that has to be removed.
	Returns:
		arr - Array. The cleaned array.
	*/
	arrRemoveByValue: function(arr,val){
		for(var i=0;i<arr.length;i++){
			if(arr[i]===val){
				arr.splice(i,1);
				break;
			}
		}
		return(arr);
	},
	/* method: arrShuffle()
	Returns shuffled array.
	Parameters:
		arr - Array.
	Returns:
		SHU - Array.
	Author: Dr.J R Stockton.
	*/
	arrShuffle: function(arr){
		var J,K,T,SHU=[].concat(arr);
		for(J=SHU.length-1;J>0;J--){
			K = this.rand(J+1);
			T = SHU[J];
			SHU[J] = SHU[K];
			SHU[K] = T;
		}
		return SHU;
	},
	/* method: arrSortByProperty()
	Sort array containing objects by specific property.
	Parameters:
		arr - Array. To be sorted.
		prop - String. Propertyname.
	Returns:
		what - Array. Sorted by specified property.
	*/
	arrSortByProperty: function(arr,prop){
		function sortByPropName(a,b){
			var x = a[prop];
			var y = b[prop];
			return((x<y)?-1:((x>y)?1:0));
		}
		arr.sort(sortByPropName);
		return(arr);
	},
	/* method: arrUnique()
	Removes duplicates from array.
	Parameters:
		arr - Array. The input array, from which duplicates are to be removed.
	Returns:
		r - Array. The cleaned array.
	*/
	arrUnique: function(arr){
		var r = [];
		o:for(var i=0,n=arr.length;i<n;i++){
			for(var x=0,y=r.length;x<y;x++) if(r[x]==arr[i]) continue o;
			r[r.length] = arr[i];
		}
		return r;
	},
	/* method: mapCoordsDirection()
	Returns the general direction of the target coordinates compared to the current (or specified) system coordinates.
	Parameters:
		posA - Vector/ID. Map coordinates in LYs or simply a system.ID.
		posB - Vector/ID. Optional. Map coordinates in LYs or simply a system.ID. Defaults to current system coordinates.
	Returns:
		str - String.
	Author: Eric Walch.
	*/
	mapCoordsDirection: function(posA,posB){
		if(typeof(posA)==='undefined' || (typeof(posA)==='object' && !posA)) return("current system");
		var pos1,pos2,dx,dy,x2,y2;
		if(typeof(posA)==='number') pos2 = System.infoForSystem(galaxyNumber,posA).coordinates;
		else pos2 = new Vector3D(posA);
		if(typeof(posB)==='number') pos1 = System.infoForSystem(galaxyNumber,posB).coordinates;
		else if(!posB) pos1 = system.info.coordinates;
		else pos1 = new Vector3D(posB);
		if(pos1.distanceTo(pos2)<0.00001) return("current system");
		dx = pos2.x-pos1.x;
		dy = pos2.y-pos1.y;
		x2 = Math.abs(dx);
		y2 = Math.abs(dy);
		if(x2>2.41421*y2) return(dx>0?"east":"west");
		if(y2>1.732*x2) return(dy<0?"north":"south");
		if(dx>0) return(dy<0?"north-east":"south-east");
		return(dy<0?"north-west":"south-west");
	},
	/* method: oxpVersionTest()
	Checks required OXPs and versions. The array is expected to contain two elements for each script and version must be null for legacy scripts!
	Parameters:
		who - String. Actual scriptname. It's only used to log for which OXP the requirements are not fullfilled.
		requires - Array. Holds for every required OXP the scriptname and version. To check only for existance set version to null.
		quiet - Boolean. Optional. If true don't log, only check.
	Returns:
		checked - Boolean.
		- false - If a requirement is not fullfilled or OXP is deactivated,
		- true - Otherwise.
	Note:
	The method checks for a property 'deactivated' in OXPs to determine if they are deactived.
	*/
	oxpVersionTest: function(who,requires,quiet){
		var checked = true;
		for(var i=0;i<requires.length;i+=2){
			if(typeof(worldScripts[requires[i]])==='undefined'){
				if(!requires[i+1]){checked = false; if(!quiet) log("Check",who+" requirement not matched : "+requires[i]+" not installed.");}
				else {checked = false; if(!quiet) log("Check",who+" requirement not matched : "+requires[i]+" min. version "+requires[i+1]+" not installed.");}
				continue;
			} else {
				if(requires[i+1] && !this.strCompareVersion(worldScripts[requires[i]].version,requires[i+1])){checked = false;
					if(!quiet) log("Check",who+" requirement not matched : "+requires[i]+" required min. version "+requires[i+1]+" not found.");
					continue;
				}
			}
			if(worldScripts[requires[i]].deactivated) checked = false;
		}
		return(checked);
	},
	/* method: oxpVersionTest2Array()
	Checks required OXPs and versions. The array is expected to contain two elements for each script and version must be null for legacy scripts!
	Parameters:
		who - String. Actual scriptname. It's only used to log for which OXP the requirements are not fullfilled.
		requires - Array. Holds for every required OXP the scriptname and version. To check only for existance set version to null.
		quiet - Boolean. Optional. If true don't log, only check.
	Returns:
		checked - Array. With values for checked elements of requires.
		- 2 - version is greater,
		- 1 - version is equal,
		- 0 - not installed,
		- -1 - OXP is deactivated,
		- -2 - version is lower.
	*/
	oxpVersionTest2Array: function(who,requires,quiet){
		var checked = [],check = 0,loop = requires.length;
		for(var i=0;i<loop;i+=2){
			if(typeof(worldScripts[requires[i]])==='undefined'){
				checked.push(0);
				if(!requires[i+1]){if(!quiet) log("Check",who+" requirement not matched : "+requires[i]+" not installed.");}
				else {if(!quiet) log("Check",who+" requirement not matched : "+requires[i]+" min. version "+requires[i+1]+" not installed.");}
				continue;
			} else {
				if(worldScripts[requires[i]].deactivated) checked.push(-1);
				else if(requires[i+1]){
					check = this.strCompareVersion(worldScripts[requires[i]].version,requires[i+1]);
					if(!check){
						checked.push(-2);
						if(!quiet) log("Check",who+" requirement not matched : "+requires[i]+" required min. version "+requires[i+1]+" not found.");
						continue;
					} else checked.push(check);
				}
			}
		}
		return(checked);
	},
	/* method: entModelView()
	Spawns or resets the modelview entity and returns it.
	Parameters:
		which - String. Optional. Role to be used for modelview. If not specified defaults to 'cabal_common_modelview'.
	Returns:
		mv - Entity. Returns modelview Entity or null.
	*/
	entModelView: function(which){
		if(!player.ship.docked) return(false);
		var mv = null;
		if(!which) which = 'cabal_common_modelview';
		if(!system.countShipsWithRole(which)) mv = system.addShips(which,1,player.ship.position,5000.0)[0];
		else mv = system.shipsWithPrimaryRole(which)[0];
		if(mv) mv.AIState = 'GLOBAL';
		return(mv);
	},
	/* method: entFaceEntity()
	This function causes ent1 to be reorientated such that it faces ent2. A common use-case is when spawning stations whose docking
	tunnel should face another entity, typically the main planet. Reorienting is instantly.
	Parameters:
		ent1 - Entity. To be reoriented.
		ent2 - Entity. To which ent1 should be oriented.
	Returns:
		nothing.
	*/
	entFaceEntity: function(ent1,ent2){
		var targetVector = ent2.position.subtract(ent1.position).direction();
		var angle = ent1.heading.angleTo(targetVector);
		var cross = ent1.heading.cross(targetVector).direction();
		ent1.orientation = ent1.orientation.rotate(cross,-angle);
		return;
	},
	/* method: entScreenCornerPos()
	Missionscreen model corner positioning.
	Parameters:
		level - Number. Works best with values between 0 and 0.35.
		signx - Number. Used as sign mantissa (-1...1) for X axis.
		signy - Number. Used as sign mantissa (-1...1) for Y axis.
		further - Number. Multiplier for Z axis. Defaults to 1.3.
	Returns:
		nothing.
	*/
	entScreenCornerPos: function(level,signx,signy,further){
		var pos = mission.displayModel.position,w = 1024/oolite.gameSettings.gameWindow.width,h = 768/oolite.gameSettings.gameWindow.height;
		pos.x = level*pos.z*signx;
		pos.y = level*pos.z*0.75*signy;
		if(further) pos.z *= further;
		else pos.z *= 1.3;
		var wh = w/h;
		pos = pos.add([w*wh-1,h*wh-1,0]);
		mission.displayModel.position=pos;
		return;
	},
	/* method: screenGCD()
	Returns greatest common denominator. Subfunction.
	Parameters:
		a - Number.
		b - Number.
	Returns:
		n - Number.
	*/
	screenGCD: function(a,b){
		return((b==0)?a:this.screenGCD(b,a%b));
	},
	/* method: screenAspect()
	Returns aspect ratio. Subfunction.
	Parameters:
		w - Number. Width.
		h - Number. Height.
		d - Number. Denominator.
	Returns:
		n - Number.
	*/
	screenAspect: function(w,h,d){
		if(!d) d = this.screenGCD(w,h);
		return((w/d)/(h/d));
	},
	/* method: screenChecks()
	Returns array with width,height,denominator and aspect ratio.
	Parameters:
		w - Number. Width. Optional.
		h - Number. Height. Optional.
	Returns:
		a - Array.
	*/
	screenChecks: function(w,h){
		if(!w) w = oolite.gameSettings.gameWindow.width;
		if(!h) h = oolite.gameSettings.gameWindow.height;
		var d = this.screenGCD(w,h);
		var a = this.screenAspect(w,h,d);
		return([w,h,d,a]);
	}
};

/* class: Cabal_Common_BinSearch
This is the main class for the binary search tree with its members. This search tree uses two corresponding entries.
*/
this.Cabal_Common_BinSearch = function(){this._cclbinsearch = null;}
Cabal_Common_BinSearch.prototype = {
	constructor: Cabal_Common_BinSearch,
	/* property: internalVersion
	Property to give OXPs a chance to check the required lib min. version easily.
	*/
	internalVersion: 15,
	/* method: add()
	Adds nodes to the search tree with the corresponding entry.
	Parameters:
		key - String. Add node with key.
		value - Object. Corresponding entry
	Returns:
		nothing
	*/
	add: function(key,value){
		var node = {key: key,left: null,right: null, value: value},current;
		if(this._cclbinsearch === null) this._cclbinsearch = node;
		else {
			current = this._cclbinsearch;
			while(true){
				if(key < current.key){
					if(current.left === null){
						current.left = node;
						break;
					} else current = current.left;
				} else if(key > current.key){
					if(current.right === null){
						current.right = node;
						break;
					} else current = current.right;
				} else break;
			}
		}
		return;
	},
	/* method: contains()
	Returns corresponing entry or false.
	Parameters:
		key - String. Search for entry.
	Returns:
		value - Object. Corresponding entry or false.
	*/
	contains: function(key){
		var found = false,current = this._cclbinsearch;
		while(!found && current){
			if(key < current.key) current = current.left;
			else if(key > current.key) current = current.right;
			else found = true;
		}
		if(!found) return (false);
		return (current.value);
	},
	// Internal
	traverse: function(process){
		function inOrder(node){
			if(node){
				if(node.left !== null) inOrder(node.left);
				process.call(this, node);
				if(node.right !== null) inOrder(node.right);
			}
		}
		inOrder(this._cclbinsearch);
	},
	/* method: size()
	Returns the number of nodes in the search tree.
	Parameters:
		none
	Returns:
		length - Number. Number of nodes in searchtree.
	*/
	size: function(){
		var length = 0;
		this.traverse(function(node){length++;});
		return length;
	},
	/* method: toArray()
	Returns an array conaining all nodes in the search tree. The nodes are processed in-order.
	Parameters:
		none
	Returns:
		result - Array. Entries in search tree.
	*/
	toArray: function(){
		var result = [];
		this.traverse(function(node){result.push(node.key,node.value);});
		return result;
	},
	/* method: toString()
	Returns a comma-separated string consisting of all entries. The nodes are processed in-order.
	Parameters:
		separator - String. Optional. If specified separator is used to separate the elements instead of a commata.
	Returns:
		result - String. Concatenation of all entries in search tree.
	*/
	toString: function(separator){
		if(separator) return this.toArray().join(separator);
		else return this.toArray().toString();
	}
};

/* class: Cabal_Common_2DCollision
This is the main class for the 2D checks with its members.
*/
this.Cabal_Common_2DCollision = function(){}
Cabal_Common_2DCollision.prototype = {
	constructor: Cabal_Common_2DCollision,
	/* property: internalVersion
	Property to give OXPs a chance to check the required lib min. version easily.
	*/
	internalVersion: 15,
	/* method: boundingBox()
	Checks if point is inside the bounding box.
	Parameters:
		minmax - Array. 4 positions in a.x,a.y,b.x,b.y
		pos - Vector. Position to be checked.
	Returns:
		inside - Boolean. True if inside.
	*/
	boundingBox: function(minmax,pos){
		if(pos.x<minmax[0] || pos.x>minmax[1] || pos.y<minmax[2] || pos.y>minmax[3]) return(false);
		return(true);
	},
	/* method: pointInPoly()
	Checks if a point is inside a polygon.
	Parameters:
		nvert - Number of vertices in the polygon.
		vertx, verty - Arrays containing the x- and y-coordinates of the polygon's vertices.
		pos - Vector. x- and y-coordinate of the test point.
		con - Boolean. Concave shapes may need to set it.
	Return:
		inside - Boolean. True if inside.
	*/
	pointInPoly: function(nvert,vertx,verty,pos,con){
		var i,j,c = false,r1=0;
		for(i=0,j=nvert-1;i<nvert;j=i++){
			if(((verty[i]>pos.y)!==(verty[j]>pos.y)) && (pos.x<(vertx[j]-vertx[i])*(pos.y-verty[i])/(verty[j]-verty[i])+vertx[i])){
				c = !c;
				r1++;
			}
			if(!con && r1>1) break;
		}
		return(c);
	},
	/* method: pointOnLine()
	Checks if point is on a specified line with a tolerance of 0.4.
	This method has a problem with vertical lines. If it needs to be more accurate use .pointOnLineB()
	Parameters:
		pa - Vector. Starting point of the line.
		pb - Vector. End point of the line.
		pos - Vector. Position to be checked.
	Return:
		online - Boolean. True if on line (within tolerance).
	*/
	pointOnLine: function(pa,pb,pos){
		var f = function(somex){return(pb.y-pa.y)/(pb.x-pa.x)*(somex-pa.x)+pa.y;};
		if((pa.x>pos.x+0.4 && pb.x>pos.x+0.4) || (pa.x<pos.x-0.4 && pb.x<pos.x-0.4)) return(false);
		if(pb.x<pa.x){var t = pa; pa = pb; pb = t;} //Switch
		if(pa.x>pb.x+0.4 || pa.x<pb.x-0.4) return(Math.abs(f(pos.x)-pos.y)<0.4 && pos.x>=pa.x && pos.x<=pb.x);
		return(((pos.y>=pa.y && pos.y<=pb.y) || (pos.y<=pa.y && pos.y>=pb.y)) && (pos.x>=pa.x && pos.x<=pb.x) || (pos.x<=pa.x && pos.x>=pb.x));
	},
	/* method: pointOnLineB()
	Checks if point is on a specified line.
	Parameters:
		p1 - Vector. Starting point of the line.
		p2 - Vector. End point of the line.
		p3 - Vector. Position to be checked.
		tol - Number. Tolerance. Default 0.01.
	Return:
		online - Boolean. True if on line (within tolerance).
	Author: Paul Bourke
	*/
	pointOnLineB: function(p1,p2,p3,tol){
		var s,rnum,denom,check,distanceLine,distanceSegment,dist1,dist2;
		if(typeof(tol)!=='number') tol = 0.01;
		rnum = (p3.x - p1.x) * (p2.x - p1.x) + (p3.y - p1.y) * (p2.y - p1.y);
		denom = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
		check = rnum / denom;
		s = ((p1.y-p3.y) * (p2.x - p1.x) - (p1.x - p3.x) * (p2.y - p1.y) ) / denom;
		distanceLine = Math.abs(s) * Math.sqrt(denom);
		if(check >= 0 && check <= 1) distanceSegment = distanceLine;
		else {
			dist1 = (p3.x - p1.x) * (p3.x - p1.x) + (p3.y - p1.y) * (p3.y - p1.y);
			dist2 = (p3.x - p2.x) * (p3.x - p2.x) + (p3.y - p2.y) * (p3.y - p2.y);
			if(dist1 < dist2) distanceSegment = Math.sqrt(dist1);
			else distanceSegment = Math.sqrt(dist2);
		}
		return((distanceSegment<tol));
	}
};

// Functionality to check systems and get more infos about the session
this.moreInfo = {
	jumpCount: 0,
	lastJumpTime: clock.absoluteSeconds,
	lastJumpDiff: 0,
	avgEnts: system.allShips.length,
	conEnts: [system.allShips.length],
	avgEntsOXP: system.allShips.length,
	conEntsOXP: [system.allShips.length],
	galaxyStats : null,
	connected : null
};
this.startUp = function()
{
	delete this.startUp;
	this.getGalaxy(); // planetinfo.plist is already processed
	this.consTimer = new Timer(this,this.checkConnection,0.1);
};
this.playerEnteredNewGalaxy = function()
{
	this.getGalaxy();
	this.consTimer = new Timer(this,this.checkConnection,0.1);
};
this.shipWillEnterWitchspace = function()
{
	if(this.delayedTimer){this.delayedTimer.stop(); delete this.delayedTimer;}
	this.moreInfo.lastJumpDiff = clock.absoluteSeconds-this.moreInfo.lastJumpTime;
	this.moreInfo.lastJumpTime = clock.absoluteSeconds;
	this.moreInfo.jumpCount++;
	this.updateGalaxyStats();
};
this.shipWillExitWitchspace = function()
{
	this.updateGalaxyStats();
};
this.shipExitedWitchspace = function()
{
	if(!system.isInterstellarSpace && this.moreInfo.jumpCount<30){
		this.moreInfo.conEnts.push(system.allShips.length);
		var n=0;
		for(var a=0;a<this.moreInfo.conEnts.length;a++) n+=this.moreInfo.conEnts[a];
		this.moreInfo.avgEnts = Math.ceil(n/this.moreInfo.conEnts.length);
		if(this.moreInfo.lastJumpDiff>10) this.delayedTimer = new Timer(this,this.delayedShipExitedWitchspace,10);
	}
};
this.delayedShipExitedWitchspace = function()
{
	if(this.moreInfo.jumpCount<30){
		this.moreInfo.conEntsOXP.push(system.allShips.length);
		var n=0;
		for(var a=0;a<this.moreInfo.conEntsOXP.length;a++) n+=this.moreInfo.conEntsOXP[a];
		this.moreInfo.avgEntsOXP = Math.ceil(n/this.moreInfo.conEntsOXP.length);
	}
	delete this.delayedTimer;
};
this.updateGalaxyStats = function()
{
	if(system.isInterstellarSpace) return;
	var inf = this.moreInfo.galaxyStats.contains(system.ID),what = ["coordinates","description","economy","government","inhabitant","inhabitants",["market","market"],"name","population","productivity","radius",["sun","sun_gone_nova"],"techlevel"];
	var l = what.length,temp;
	for(var i=0;i<l;i++){
		temp = what[i];
		if(typeof(temp)!=="string"){
			if(inf[temp[0]]===system.info[temp[1]]) continue;
			if(typeof(inf[temp[0]])!=="undefined") inf[temp[0]] = (system.info[temp[1]]?false:true);
		} else {
			if(inf[temp]===system.info[temp]) continue;
			inf[temp] = system.info[temp];
		}
	}
	return;
};
this.getGalaxy = function()
{
	this.moreInfo.galaxyStats = new this.Cabal_Common_BinSearch(); // Binary tree by ID
	var c,gn=galaxyNumber,sy,r;
	this.gInfo = [];
	// Accessing system properties adds >250 ms
	// except coordinates - this adds only ~4 ms
	for(var i=0;i<256;i++){
		c=System.infoForSystem(gn,i);
		sy = {
			coordinates: {
				x:c.coordinates.x,
				y:c.coordinates.y,
				z:c.coordinates.z
			},
			description: c.description,
			economy: c.economy,
			government: c.government,
			ID: i,
			inhabitant: c.inhabitant,
			inhabitants: c.inhabitants,
			market: c.market,
			name: c.name,
			population: c.population,
			productivity: c.productivity,
			radius: c.radius,
			sun: (c.sun_gone_nova?false:true),
			techlevel: c.techlevel
		};
		r = {
			x: c.coordinates.x,
			y: c.coordinates.y,
			id: i,
			done: 0
		};
		this.gInfo.push(r);
		this.moreInfo.galaxyStats.add(i,sy);
	}
	return;
};
// Find connections
this.checkConnection = function()
{
	var helper = new this.Cabal_Common();
	var m = 1.0214285714285714285714285714286;
	var check = [],cur = [],cpp = player.ship.galaxyCoordinatesInLY,dist,temp;
	var cp = {
		x: cpp.x,
		y: cpp.y,
		z: 0
	};
	this.gInfo = helper.arrSortByProperty(this.gInfo,"x");
	this.moreInfo.connected = [];
	// Start loop
	for(var i=0;i<256;i++){
		temp = this.gInfo[i];
		if(temp.x>cp.x+7.15) break;
		if(temp.x<cp.x-7.15 || temp.y<cp.y-7.15 || temp.y>cp.y+7.15) continue;
		dist = Math.sqrt(((cp.x-temp.x)*(cp.x-temp.x)*m)+(cp.y-temp.y)*(cp.y-temp.y));
		if(dist<7.341) cur.push(temp);
	}
	// Grow
	while(cur.length){
		check = check.concat(cur);
		cur = [];
		for(var o=0;o<check.length;o++){
			cp = check[o];
			if(cp.id===system.ID || cp.done) continue;
			for(var i=0;i<256;i++){
				temp = this.gInfo[i];
				if(temp.x>cp.x+7.15) break;
				if(temp.x<cp.x-7.15 || temp.y<cp.y-7.15 || temp.y>cp.y+7.15) continue;
				dist = Math.sqrt(((cp.x-temp.x)*(cp.x-temp.x)*m)+(cp.y-temp.y)*(cp.y-temp.y));
				if(dist<7.341 && check.indexOf(temp)===-1 && cur.indexOf(temp)===-1) cur.push(temp);
			}
		}
		for(var m=0;m<check.length;m++){
			temp = this.gInfo.indexOf(check[m]);
			this.gInfo[temp].done = 1;
		}
	}
	for(var t=0;t<check.length;t++) this.moreInfo.connected.push(check[t].id);
	if(this.consTimer) delete this.consTimer;
	delete this.gInfo;
	return;
};
Scripts/Cabal_Common_Keyboard.js
"use strict";
this.name = "Cabal_Common_Keyboard";
this.author = "Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Libraryscript Keyboard";
this.version = "1.7";
/* function: start()
Parameters:
	who - String. The name of a worldScript which implements the keyboard callback.
	min - Number. Optional - minimum length for completion, default(4).
	mode - Number. Optional - 1(only alphabetical), 2(only numeric), 3(hexadecimal), default 4(alphanumeric).
	scpic - String. Optional. Name for screenbackground build upon scpic + suffix. The mode defines which one will be used.
	scovl - String. Optional. Filename for screenoverlay (with fileextension). Default null.
	scmod - String. Optional. Role for the used model. Default 'cabal_common_key'.

Returns:
nothing
> worldScripts.Cabal_Common_Keyboard.start(this.name,5,1);

Suffixes are :
- 1 - '_keyboard_alpha.png'
- 2 - '_keyboard_nums.png'
- 3 - '_keyboard_hex.png'
- 4 - '_keyboard.png'
*/
this.start = function(who,min,mode,scpic,scovl,scmod)
{
	if(!player.ship.docked) return;
	this.keyboard = new Object({ini: true,input: "",keyboardSound: new SoundSource(),loop: false,sound: 'cabal_common_key.ogg',
		maxRows: 4,model: 'cabal_common_key',modelSC: null,pic: 'cabal_common_keyboard.png',overlay: null,ratioX: 1,ratioY: 1,
		rowsT: [],rowsX: [],rowsY: [],toCall: null,toMin: 4,X: 4,Y: 2});
	if(who) this.keyboard.toCall = who;
	if(min && !isNaN(min) && min>0) this.keyboard.toMin = min;
	else this.keyboard.toMin = 4;
	var rowsY_c = [69,46,23,-1],rowsX_c = [[-109,-85,-61,-36,-12.5,12,36.5,61,85,110],[-109.5,-85,-60.5,-36,-12.5,12.5,36.5,61,85,110],[-99.5,-75,-51,-26,-2,22,46.5,71,95],[-86,-61.5,-37.5,-13,12,36,61]];
	var rowsT_c = [["1","2","3","4","5","6","7","8","9","0"],["Q","W","E","R","T","Y","U","I","O","P"],["A","S","D","F","G","H","J","K","L"],["Z","X","C","V","B","N","M"]];
	var rowsY_ch = [69,46],rowsX_ch = [[-109,-85,-61,-36,-12.5,12,36.5,61,85,110],[-60.5,-36,-12.5,12.5,36.5,61]];
	var rowsT_ch = [["1","2","3","4","5","6","7","8","9","0"],["A","B","C","D","E","F"]];
	switch(mode){
		case 1: this.keyboard.rowsX = [rowsX_c[1],rowsX_c[2],rowsX_c[3]];
			this.keyboard.rowsY = [rowsY_c[1],rowsY_c[2],rowsY_c[3]];
			this.keyboard.rowsT = [rowsT_c[1],rowsT_c[2],rowsT_c[3]];
			this.keyboard.Y = 1;
			this.keyboard.maxRows = 2;
			if(scpic) this.keyboard.pic = scpic+'_keyboard_alpha.png';
			else this.keyboard.pic = 'cabal_common_keyboard_alpha.png';
			break;
		case 2: this.keyboard.rowsX = [rowsX_c[0]];
			this.keyboard.rowsY = [rowsY_c[0]];
			this.keyboard.rowsT = [rowsT_c[0]];
			this.keyboard.Y = 0;
			this.keyboard.maxRows = 0;
			if(scpic) this.keyboard.pic = scpic+'_keyboard_nums.png';
			else this.keyboard.pic = 'cabal_common_keyboard_nums.png';
			break;
		case 3: this.keyboard.rowsX = rowsX_ch;
			this.keyboard.rowsY = rowsY_ch;
			this.keyboard.rowsT = rowsT_ch;
			this.keyboard.Y = 0;
			this.keyboard.maxRows = 1;
			if(scpic) this.keyboard.pic = scpic+'_keyboard_hex.png';
			else this.keyboard.pic = 'cabal_common_keyboard_hex.png';
			break;
		default: this.keyboard.rowsX = rowsX_c;
			this.keyboard.rowsY = rowsY_c;
			this.keyboard.rowsT = rowsT_c;
			this.keyboard.Y = 2;
			this.keyboard.maxRows = 3;
			if(scpic) this.keyboard.pic = scpic+'_keyboard.png';
			else this.keyboard.pic = 'cabal_common_keyboard.png';
	}
	this.keyboard.X = 4;
	var r = (oolite.gameSettings.gameWindow.width/oolite.gameSettings.gameWindow.height),c = 1024/768;
	if(r>c){
		var ccX = (r%c)/1.5,ccY = (r%c)/2;
		this.keyboard.ratioX = 1-ccX;
		this.keyboard.ratioY = 1-ccY;
	} else {
		this.keyboard.ratioX = 1;
		this.keyboard.ratioY = 1;
	}
	if(!player.ship.docked){
		if(this.keyboard) delete this.keyboard;
		return;
	}
	if(scovl) this.keyboard.overlay = scovl;
	if(scmod) this.keyboard.model = scmod;
	this.keyboard.keyboardSound.loop = false;
	this.keyboard.keyboardSound.sound = 'cabal_common_key.ogg';
	this.keyboardShow();
	this.keyboard.modelSC.lightsActive = true;
	return;
};
/* function: keyboardEval()
Workaround to change the context of this!!!
*/
this.keyboardEval = function(choice){worldScripts.Cabal_Common_Keyboard.keyboardChoice(choice); return;};
/* function: keyboardChoice()
Evaluates the user choice.
When <keyboard.toCall> is specified and user confirms or aborts the keyboard input.

Callback:
- "" - String. if user aborts.
- <keyboard.input> - String. If user confirmed input.
> worldScripts[this.keyboard.toCall].Cabal_Common_Keyboard_Output(this.keyboard.input);
*/
this.keyboardChoice = function(choice)
{
	switch(choice){
		case 'CABAL_COMMON_KEYBOARDA':
			this.keyboard.X--;
			if(this.keyboard.X<0) this.keyboard.X = this.keyboard.rowsX[this.keyboard.Y].length-1;
			this.keyboardShow(); break;
		case 'CABAL_COMMON_KEYBOARDB':
			this.keyboard.Y--;
			if(this.keyboard.Y<0) this.keyboard.Y = this.keyboard.maxRows;
			if(this.keyboard.X>this.keyboard.rowsX[this.keyboard.Y].length-1) this.keyboard.X = this.keyboard.rowsX[this.keyboard.Y].length-1;
			this.keyboardShow(); break;
		case 'CABAL_COMMON_KEYBOARDC':
			this.keyboard.Y++;
			if(this.keyboard.Y>this.keyboard.maxRows) this.keyboard.Y = 0;
			if(this.keyboard.X>this.keyboard.rowsX[this.keyboard.Y].length-1) this.keyboard.X = this.keyboard.rowsX[this.keyboard.Y].length-1;
			this.keyboardShow(); break;
		case 'CABAL_COMMON_KEYBOARDD':
			this.keyboard.input += this.keyboard.rowsT[this.keyboard.Y][this.keyboard.X];
			this.keyboardShow(); break;
		case 'CABAL_COMMON_KEYBOARDE':
			if(this.keyboard.input.length) this.keyboard.input = this.keyboard.input.substr(0,this.keyboard.input.length-1);
			this.keyboardShow(); break;
		case 'CABAL_COMMON_KEYBOARDY':
			if(this.keyboard.toCall) worldScripts[this.keyboard.toCall].Cabal_Common_Keyboard_Output("");
			this.killModelview(); break;
		case 'CABAL_COMMON_KEYBOARDZ':
			if(this.keyboard.toCall) worldScripts[this.keyboard.toCall].Cabal_Common_Keyboard_Output(this.keyboard.input);
			this.killModelview(); break;
	}
	return;
};
this.keyboardShow = function()
{
	this.keyboard.keyboardSound.play();
	if(this.keyboard.input.length<this.keyboard.toMin){
		if(this.keyboard.overlay) mission.runScreen({title:"Terminal",choicesKey:'CABAL_COMMON_KEYBOARD_A',model:this.keyboard.model,spinModel:false,background:this.keyboard.pic,overlay:this.keyboard.overlay},this.keyboardEval);
		else mission.runScreen({title:"Terminal",choicesKey:'CABAL_COMMON_KEYBOARD_A',model:this.keyboard.model,spinModel:false,background:this.keyboard.pic},this.keyboardEval);
	} else {
		if(this.keyboard.overlay) mission.runScreen({title:"Terminal",choicesKey:'CABAL_COMMON_KEYBOARD_B',model:this.keyboard.model,spinModel:false,background:this.keyboard.pic,overlay:this.keyboard.overlay},this.keyboardEval);
		else mission.runScreen({title:"Terminal",choicesKey:'CABAL_COMMON_KEYBOARD_B',model:this.keyboard.model,spinModel:false,background:this.keyboard.pic},this.keyboardEval);
	}
	this.keyboard.modelSC = mission.displayModel;
	this.keyboard.modelSC.orientation = [1,-1,0,0]; // Makes the model invisible
	if(this.keyboard.ini){
		this.keyboard.modelSC.position = [this.keyboard.rowsX[this.keyboard.Y][4]*this.keyboard.ratioX,this.keyboard.rowsY[this.keyboard.Y]*this.keyboard.ratioY,500];
		delete this.keyboard.ini;
	} else this.keyboard.modelSC.position = [this.keyboard.rowsX[this.keyboard.Y][this.keyboard.X]*this.keyboard.ratioX,this.keyboard.rowsY[this.keyboard.Y]*this.keyboard.ratioY,500];
	mission.addMessageText("\n    User input: "+this.keyboard.input+"\n\n\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\f\fMinimum length: "+this.keyboard.toMin+" signs.");
	this.keyboard.modelSC.lightsActive = true;
	return;
};
this.killModelview = this.shipWillLaunchFromStation = function()
{
	if(this.keyboard) delete this.keyboard;
	return;
};
Scripts/Cabal_Common_MissionHandling.js
"use strict";
this.name = "Cabal_Common_MissionHandling";
this.author = "Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Coordinate mission offerings based on system descriptions.";
this.version = "1.7";

this.mPool = [];
this.startUp = function()
{
	delete this.startUp;
	this.helper = new worldScripts.Cabal_Common_Functions.Cabal_Common();
	this.binSearch = new worldScripts.Cabal_Common_Functions.Cabal_Common_BinSearch();
	this.checked = false;
	this.runningMission = false;
	this.logging = false;
};
this.prepare = function()
{
	var s = [],r = this.mPool.length;
	if(r){
		var merged = [],pos=0;
		for(var m=0;m<r;m++){
			s = this.mPool[m];
			if(s.length!==2 || !s[1].name || typeof(s[1].name)!=='string' || !s[1].func || typeof(s[1].func)!=='string') continue;
			if(s[1].hasOwnProperty("incOXP")){
				var temp = s[1].incOXP;
				for(var i=0;i<temp.length;i++){
					if(worldScripts[temp[i]]!=='undefined'){
						log(this.name,"Dropping token "+s[0]+" : "+s[1].name+"."+s[1].func+" because of incompatibility with "+temp[i]+".");
						continue;
					}
				}
			}
			if(!s[1].hasOwnProperty("mutalEx") || typeof(s[1].mutalEx)!=='number') s[1].mutalEx = 0;
			pos = merged.indexOf(s[0]);
			if(pos===-1) merged.push(s[0],{0:s[1]});
			else {
				var q = merged[pos+1];
				for(var p=1;p<10;p++){
					if(q.hasOwnProperty(p)) continue;
					q[p] = s[1];
					break;
				}
			}
		}
		r = merged.length;
		for(var i=0;i<r;i+=2){
			this.binSearch.add(merged[i],merged[i+1]);
		}
		if(this.logging) log(this.name,"merged: "+JSON.stringify(merged));
	} else {
		delete this.performCheck;
		delete this.shipWillExitWitchspace;
		delete this.shipWillLaunchFromStation;
		this.checked = true;
	}
	delete this.alertConditionChanged;
	delete this.guiScreenChanged;
	delete this.prepare;
	return;
};
this.performCheck = function(early)
{
	this.checked = true;
	if(this.runningMission) return;
	var test = system.description,prop;
	var pRand = this.helper.pseudoRand(system.ID,true);
	var rest = test.replace(/[^a-zA-Z]/g,' ');
	rest = rest.split(" ");
	rest = this.helper.arrUnique(rest);
	var fflag = false, l = rest.length, muteGroup = [];
	for(var s=0;s<l;s++){
		fflag = this.binSearch.contains(rest[s]);
		if(fflag && typeof(fflag)!=='false'){
			for(prop in fflag){
				var c = fflag[prop];
				if((early && !c.early) || (!early && c.early)) continue;
				if(muteGroup.indexOf(c.mutalEx)!==-1) continue;
				if(c.chance && Math.random()>c.chance) continue;
				if(c.gal && c.gal.indexOf(galaxyNumber)===-1) continue;
				if(c.ID && c.ID.indexOf(system.ID)===-1) continue;
				if(c.gov && c.gov.indexOf(system.government)===-1) continue;
				if(c.seeds && c.seeds.indexOf(pRand)===-1) continue;
				if(c.exToken && rest.indexOf(c.exToken)!==-1) continue;
				if(this.logging) log(this.name,"check: "+c.name+" - muteGroup: "+c.mutalEx+" rest[s]:"+rest[s]);
				if(worldScripts[c.name]){
					var act = false;
					if(c.func && worldScripts[c.name][c.func]) act = worldScripts[c.name][c.func](rest[s]);
					if(act){
						muteGroup.push(c.mutalEx);
						this.runningMission = true;
					}
				}
			}
		}
	}
	return;
};
this.alertConditionChanged = this.guiScreenChanged = function()
{
	if(this.prepare) this.prepare();
	if(!this.checked && this.performCheck) this.performCheck();
};
this.shipWillExitWitchspace = function()
{
	this.runningMission = false;
	if(this.performCheck) this.performCheck(1);
};
this.shipExitedWitchspace = function()
{
	if(this.performCheck) this.performCheck();
};
this.shipWillLaunchFromStation = function()
{
	if(this.prepare) this.prepare();
	if(!this.checked && this.performCheck) this.performCheck(1);
};
this.shipLaunchedFromStation = function()
{
	if(!this.checked && this.performCheck) this.performCheck();
};
Scripts/Cabal_Common_Music.js
"use strict";
this.name = "Cabal_Common_Music";
this.author = "Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Music helper for missions";
this.version = "1.7";

this.startUp = function()
{
	delete this.startUp;
	this.helper = new worldScripts.Cabal_Common_Functions.Cabal_Common();
	this.mediaFade = new SoundSource();
	this.mediaFade.sound = "[cabal_common_music_fade1]";
	this.mediaQueue = null;
	this.mediaPool = {
		allListed:[],
		aegis:{enter:[],exit:[]},
		alert:{red:[],yellow:[],green:[]},
		destroyed:{ent:[]},
		exitWS:{inter:[],goneNova:[],doNova:[],standard:[],any:[]},
		launch:{ent:[],inter:[],goneNova:[],doNova:[],main:[],any:[]},
		planet:{ent:[],enterMain:[],enterSun:[],exitMain:[],exitSun:[]},
		scooped:{ent:[],fuel:[]},
		track:{byName:[],byRole:[]}
	};
	this.currentFile = null;
	this.currentTrack = null;
	this.requestHandlerSet = [];
	this.blockAlert = 1;
	this.blockPlanet = 1;
	this.blockFuel = 1;
	var handler = Object.keys(this.mediaPool);
	handler.shift();
	for(var i=0;i<handler.length;i++){
		var c = Object.keys(this.mediaPool[handler[i]]);
		c.unshift(handler[i]);
		this.requestHandlerSet.push(c);
	}
};
// Register events
this.addMedia = function(obj)
{
	if(this.startUp) this.startUp();
	if(!obj || !Object.isExtensible(obj) || !obj.who || !worldScripts[obj.who] || !obj.hasOwnProperty("start")) return(false);
	var pid = -1;
	if(this.mediaPool.allListed.length){
		for(var i=0;i<this.mediaPool.allListed.length;i++){
			if(obj.who===this.mediaPool.allListed[i][0]){
				pid = i;
				break;
			}
		}
	}
	if(pid===-1){
		pid = this.mediaPool.allListed.length;
		this.mediaPool.allListed.push([obj.who,obj.start]);
	}
	for(var handler in this.mediaPool){
		if(handler==="allListed") continue;
		if(obj.hasOwnProperty(handler)){
			for(var what in this.mediaPool[handler]){
				if(obj[handler].hasOwnProperty(what)){
					var cur = obj[handler][what];
					for(var i=0;i<cur.length;i++){
						cur[i].sid = pid;
						this.mediaPool[handler][what].push(cur[i]);
					}
				}
			}
		}
	}
	if(!player.ship.docked) this.checkTrack();
	return(true);
};
// Unregister events
this.removeMedia = function(who)
{
	if(!who) return(false);
	var pid;
	for(var i=0;i<this.mediaPool.allListed.length;i++){
		if(who===this.mediaPool.allListed[i][0]){
			pid = i;
			this.mediaPool.allListed = this.helper.arrRemoveByValue(this.mediaPool.allListed,this.mediaPool.allListed[pid]);
			break;
		}
	}
	if(typeof(pid)==="undefined") return(false);
	for(var handler in this.mediaPool){
		if(handler==="allListed") continue;
		for(var what in this.mediaPool[handler]){
			for(var i=0;i<this.mediaPool[handler][what].length;i++){
				if(this.mediaPool[handler][what][i].sid===pid){
					this.mediaPool[handler][what] = this.helper.arrRemoveByValue(this.mediaPool[handler][what],this.mediaPool[handler][what][i]);
					if(i>-1) i--;
				} else if(this.mediaPool[handler][what][i].sid>pid) this.mediaPool[handler][what][i].sid--;
			}
		}
	}
	return(true);
};
// Change status flag
this.changeStatus = function(who,status)
{
	for(var i=0;i<this.mediaPool.allListed.length;i++){
		if(who===this.mediaPool.allListed[i][0]){
			this.mediaPool.allListed[i][1] = status;
			if(!player.ship.docked) this.checkTrack();
			return(true);
		}
	}
	return(false);
};
this.cclm_findActive = function(element){return(this.mediaPool.allListed[element.sid][1]);};
this.performMedia = function(handler,specifier,followup)
{
	var cur,found=false;
	// Handle ent + followup
	if(followup && this.mediaPool[handler].hasOwnProperty("ent")){
		cur = this.mediaPool[handler].ent.filter(this.cclm_findActive,this);
		if(cur.length) found = this.loopChecks(cur,followup);
		if(found && (handler==="aegis" || handler==="planet")) this.blockPlanet = clock.absoluteSeconds+10;
		return;
	}
	// Handle standard specifier
	if(!found){
		cur = this.mediaPool[handler][specifier].filter(this.cclm_findActive,this);
		if(cur.length) found = this.loopChecks(cur);
		if(found && handler==="scooped" && specifier==="fuel") this.blockFuel = clock.absoluteSeconds+10;
		if(!found){
			// Handle - any
			if(this.mediaPool[handler].hasOwnProperty("any")){
				cur = this.mediaPool[handler].any.filter(this.cclm_findActive,this);
				if(!cur.length) return;
				found = this.loopChecks(cur);
			}
		}
	}
	if(found && (handler==="aegis" || handler==="planet")) this.blockPlanet = clock.absoluteSeconds+10;
	this.blockAlert = clock.absoluteSeconds;
	return;
};
this.isClose = function(entity)
{
	return(entity.isShip && entity.isValid);
};
this.performTracker = function()
{
	if(!player.ship.isValid){
		this.trackerTimer.stop();
		return;
	}
	var ents = system.filteredEntities(this,this.isClose,player.ship,25600);
	var cur = this.mediaPool.track.byName.filter(this.cclm_findActive,this),disable = true;
	for(var i=0;i<cur.length;i++){
		var sub = cur[i];
		if(!this.mediaPool.allListed[sub.sid][1]) continue;
		disable = false;
		var ws = this.mediaPool.allListed[sub.sid][0];
		if(sub.hasOwnProperty("prop") && !worldScripts[ws][sub.prop]) continue;
		for(var e=0;e<ents.length;e++){
			if(sub.ent!==ents[e].name || sub.ent===this.currentTrack) continue;
			if(sub.dist<player.ship.position.distanceTo(ents[e].position)) continue;
			if(sub.fadeQ){
				this.mediaFade.sound = "[cabal_common_music_fade"+sub.fadeQ+"]";
				this.mediaFade.play();
			}
			var cl = 1, bp = 0, rp = 0, nq = 0;
			if(sub.bypassQ){
				bp = sub.bypassQ;
				cl = 0;
			}
			if(sub.leaveQ) cl = 0;
			if(sub.onlyRepQ) rp = 1;
			if(sub.forceQ) nq = 1;
			this.runMedia(sub.music,cl,bp,rp,nq);
			this.currentTrack = sub.ent;
			return(true);
		}
	}
	// If all OXPs are off stop - re-enabling via changeStatus
	if(disable){
		this.trackerTimer.stop();
		delete this.trackerTimer;
	}
	return(false);
};
this.loopChecks = function(cur,followup)
{
	for(var i=0;i<cur.length;i++){
		var sub = cur[i];
		if(!this.mediaPool.allListed[sub.sid][1]) continue;
		var ws = this.mediaPool.allListed[sub.sid][0];
		if(sub.hasOwnProperty("prop") && !worldScripts[ws][sub.prop]) continue;
		if(followup && followup!==sub.ent) continue;
		if(sub.fadeQ){
			this.mediaFade.sound = "[cabal_common_music_fade"+sub.fadeQ+"]";
			this.mediaFade.play();
		}
		var cl = 1, bp = 0, rp = 0, nq = 0;
		if(sub.bypassQ){
			bp = sub.bypassQ;
			cl = 0;
		}
		if(sub.leaveQ) cl = 0;
		if(sub.onlyRepQ) rp = 1;
		if(sub.forceQ) nq = 1;
		this.runMedia(sub.music,cl,bp,rp,nq);
		return(true);
	}
	return(false)
};
this.runMedia = function(list,clear,bypass,replace,force)
{
	if(typeof(list)==="string"){
		Sound.playMusic(list);
		this.currentFile = list;
		if(clear) this.mediaQueue = null;
		else if(bypass){
			if(this.mediaTimer){
				if(this.mediaTimer.isRunning){
					this.mediaTimer.stop();
					this.mediaTimer.nextTime = clock.absoluteSeconds+bypass;
					this.mediaTimer.start();
				}
			}
		}
	} else {
		var flag=false;
		if((replace && !this.mediaQueue) || force) flag=true;
		this.mediaQueue = list;
		if(flag){
			if(force) this.doMediaTimer(1);
			else if(replace) this.doMediaTimer();
		}
	}
	return;
};
this.doMediaTimer = function(force)
{
	if(this.mediaTimer){
		if(this.mediaTimer.isRunning) this.mediaTimer.stop();
		delete this.mediaTimer;
	}
	if(!this.mediaQueue) return;
	var entry = 0;
	if(!force) entry = Math.floor(Math.random()*this.mediaQueue.length);
	var dura = this.mediaQueue[entry].duration;
	var rep = this.mediaQueue[entry].repeat;
	var delay = (dura+1)*(rep?rep:1);
	Sound.playMusic(this.mediaQueue[entry].file,rep);
	this.currentFile = this.mediaQueue[entry].file;
	this.mediaTimer = new Timer(this,this.doMediaTimer,delay);
	return;
};
this.shipWillDockWithStation = this.shipWillEnterWitchspace = this.shipDied = function()
{
	if(this.mediaTimer){
		if(this.mediaTimer.isRunning) this.mediaTimer.stop();
		delete this.mediaTimer;
	}
	if(this.trackerTimer){
		if(this.trackerTimer.isRunning) this.trackerTimer.stop();
		delete this.trackerTimer;
	}
	this.mediaQueue = null;
	this.currentTrack = null;
	this.currentFile = null;
};
this.shipWillLaunchFromStation = this.shipWillExitWitchspace = function()
{
	this.checkTrack();
};
this.shipTargetLost = function(target)
{
	if(!player.ship.isValid) return;
	if(target && target.name && target.name===this.currentTrack && target.position.distanceTo(player.ship)>25600) this.currentTrack = null;
};
this.checkTrack = function()
{
	if(!this.trackerTimer && this.mediaPool.track.byName.filter(this.cclm_findActive,this).length) this.trackerTimer = new Timer(this,this.performTracker,0,10);
	return;
};
this.alertConditionChanged = function(to)
{
	if(!player.ship.isValid || player.ship.status!=="STATUS_IN_FLIGHT") return;
	switch(to){
		case 3: this.performMedia("alert","red"); break;
		case 2: if(clock.absoluteSeconds>this.blockAlert) this.performMedia("alert","yellow"); break;
		case 1: if(clock.absoluteSeconds>this.blockAlert) this.performMedia("alert","green"); break;
	}
	this.blockAlert = clock.absoluteSeconds+10;
};
this.shipEnteredPlanetaryVicinity = function(planet)
{
	if(!player.ship.isValid || !planet || clock.absoluteSeconds<this.blockPlanet) return;
	if(planet.isMainPlanet) this.performMedia("planet","enterMain");
	else if(planet.isSun) this.performMedia("planet","enterSun");
	else this.performMedia("planet","ent",planet.radius);
};
this.shipEnteredStationAegis = function(station)
{
	if(!player.ship.isValid || !station || clock.absoluteSeconds<this.blockPlanet) return;
	this.performMedia("aegis","enter");
};
this.shipExitedPlanetaryVicinity = function(planet)
{
	if(!player.ship.isValid || !planet || clock.absoluteSeconds<this.blockPlanet) return;
	if(planet.isMainPlanet) this.performMedia("planet","exitMain");
	if(planet.isSun) this.performMedia("planet","exitSun");
};
this.shipExitedStationAegis = function(station)
{
	if(!player.ship.isValid || !station || clock.absoluteSeconds<this.blockPlanet) return;
	this.performMedia("aegis","exit");
};
this.shipExitedWitchspace = function()
{
	if(!player.ship.isValid) return;
	if(system.isInterstellarSpace) this.performMedia("exitWS","inter");
	else if(system.sun.hasGoneNova) this.performMedia("exitWS","goneNova");
	else if(system.sun.isGoingNova) this.performMedia("exitWS","doNova");
	else this.performMedia("exitWS","standard");
	this.checkTrack();
};
this.shipLaunchedFromStation = function(station)
{
	if(!player.ship.isValid || !station) return;
	if(station.isMainStation) this.performMedia("launch","main");
	else this.performMedia("launch","ent",station.name);
};
this.shipScoopedOther = function(whom)
{
	if(!player.ship.isValid || !whom || !whom.name) return;
	this.performMedia("scooped","ent",whom.name);
};
this.shipScoopedFuel = function()
{
	if(!player.ship.isValid || clock.absoluteSeconds<this.blockFuel) return;
	this.performMedia("scooped","fuel");
};
this.shipTargetDestroyed = function(target)
{
	if(!player.ship.isValid || !target || !target.name) return;
	this.performMedia("destroyed","ent",target.name);
};
Scripts/Cabal_Common_OXPStrength.js
"use strict";
this.name = "Cabal_Common_OXPStrength";
this.author = "Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Checks and/or cleans out entities and suspends OXPs.";
this.version = "1.7.1";

this.ccl_strengthExclEntByName = []; // Array of entity names to be excluded
this.startUp = function()
{
	delete this.startUp; // PhantorGorth' chain handling
	this.checkOXPStrength = new this.Cabal_Common_OXPStrengthAPI(); // Get instance
	this.helper = new worldScripts.Cabal_Common_Functions.Cabal_Common();
	this.gJump = false; // Flag for galactic jump
	this.current = null; // Holds current settings object or null
	this.watchLocations = [[],[],[],[],[],[],[],[]]; // Storage for settings splitted in galaxies to keep loops small
	this.resetValues(); // Default settings
	this.leftSystem = system.ID; // For interstellar checks
	this.suspendedOXPs = []; // Holds suspended OXPs
	for(var w in worldScripts){
		if(!w) continue;
		var s = worldScripts[w];
		if(!s) continue;
		for(var prop in s){
			var p = s[prop];
			if(!prop || !p) continue;
			if(typeof(p)==="function"){
				var temp = p.toString();
				if(temp.search(/restoreSubEntities/gi)!==-1) this.checkOXPStrength.wayoff.push(w.toLowerCase());
				if(temp.search(/EQ_RENOVATION/gi)!==-1) this.checkOXPStrength.wayoff.push(w.toLowerCase());
			}
		}
	}
};
this.guiScreenChanged = this.alertConditionChanged = this.missionScreenOpportunity = function()
{
	delete this.guiScreenChanged;
	delete this.alertConditionChanged;
	delete this.missionScreenOpportunity;
	// Excluded entities?
	if(this.ccl_strengthExclEntByName.length){
		for(var i=0;i<this.ccl_strengthExclEntByName.length;i++){
			if(typeof(this.ccl_strengthExclEntByName[i])==="string") this.checkOXPStrength.binAdd(this.ccl_strengthExclEntByName[i],true);
			else {
				for(var j=0;j<this.ccl_strengthExclEntByName[i].length;j++){
					this.checkOXPStrength.binAdd(this.ccl_strengthExclEntByName[i][j],true);
				}
			}
		}
	}
	this.checkWatchLocations(system.ID,false);
};
// Interface for other OXPS to register settings
this.registerLocation = function(obj)
{
	if(obj && obj.hasOwnProperty("Gal") && obj.hasOwnProperty("ID") && obj.hasOwnProperty("ISS") && obj.CSS && obj.CSP){
		this.watchLocations[obj.Gal].push(obj);
		return(true);
	}
	return(false);
};
// Oktis approach - callback
this.suspendOXP = function()
{
	if(this.current && this.suspendedOXPs.length) return(true);
	return(false);
};
this.playerWillSaveGame = function()
{
	this.tempS = missionVariables.CCL_OXPStrength;
	missionVariables.CCL_OXPStrength = 9; //Save only default
	this.guiScreenChanged = function(){
		missionVariables.CCL_OXPStrength = this.tempS;
		delete this.guiScreenChanged;
	}
};
// Defaults
this.resetValues = function(full)
{
	this.checkOXPStrength.checkCoef = 999999; // Minimum coef
	this.checkOXPStrength.checkKill = 999999; // Minimum coef for removing
	this.checkOXPStrength.logIt = false; // Logging flag
	this.checkOXPStrength.checkLocation = 0; // Location to be checked
	missionVariables.CCL_OXPStrength = 9; // For conditions in shipdata
	this.current = null;
	if(full) this.suspendedOXPs = [];
	return;
};
this.playerStartedJumpCountdown = function(jump)
{
	if(jump==="galactic") this.gJump = true;
};
this.playerCancelledJumpCountdown = this.playerJumpFailed = function()
{
	this.gJump = false;
};
// Interstellar and normal space checks
this.shipWillExitWitchspace = this.shipWillLaunchFromStation = function()
{
	this.gJump = false;
	if(system.isInterstellarSpace) this.checkWatchLocations(this.leftSystem,true);
	else this.checkWatchLocations(system.ID,false);
	if(this.current) this.startCleaning(this.current);
	else this.resetValues(1);
};
// Reset and prepare next checks. MV has to be set before jumping to prevent spawning by populator
this.shipWillEnterWitchspace = function()
{
	if(this.repeatTimer){
		this.repeatTimer.stop();
		delete this.repeatTimer;
	}
	this.leftSystem = system.ID;
	if(this.current && !this.current.Permanent) this.watchLocations[galaxyNumber] = this.checkOXPStrength.arrRemoveByValue(this.watchLocations[galaxyNumber],this.current);
	this.resetValues();
	if(!this.gJump){
		if(player.ship.scriptedMisjump) this.checkWatchLocations(this.leftSystem,true);
		else this.checkWatchLocations(player.ship.targetSystem,false);
		this.current = null;
	} else {
		var loc = player.ship.galacticHyperspaceFixedCoordsInLY.toArray(); // Rounding!!!
		for(var r=0;r<3;r++){
			loc[r] = parseFloat(loc[r].toFixed(1));
		}
		this.checkWatchLocations(loc,false);
	}
};
// Check if it should be activated
this.checkWatchLocations = function(ID,ISS)
{
	var c=[],temp;
	// Grab subset
	if(this.gJump){
		if(galaxyNumber<7) c = this.watchLocations[galaxyNumber+1];
		else c = this.watchLocations[0];
	} else c = this.watchLocations[galaxyNumber];
	for(var i=0;i<c.length;i++){
		if(c[i].ID===ID || (this.gJump && c[i].GJump && c[i].GJump[0]===ID[0] && c[i].GJump[1]===ID[1])){
			temp = null;
			if(c[i].CSS && c[i].CSP && !worldScripts[c[i].CSS][c[i].CSP]) continue; // Check status
			if((system.isInterstellarSpace || player.ship.scriptedMisjump) && ISS && c[i].ISS) temp = c[i];
			else if(!system.isInterstellarSpace && !player.ship.scriptedMisjump && !ISS && !c[i].ISS) temp = c[i];
			else if(system.isInterstellarSpace && !player.ship.scriptedMisjump && !ISS && !c[i].ISS) temp = c[i];
			if(temp){
				if(!this.current) this.current = temp;
				else {
					if(temp.Strength && temp.Strength<this.current.Strength || !this.current.hasOwnProperty("Strength")) this.current.Strength = temp.Strength;
					if(temp.checkC && temp.checkC<this.current.checkC || !this.current.hasOwnProperty("checkC")) this.current.checkC = temp.checkC;
					if(temp.checkK && temp.checkK<this.current.checkK || !this.current.hasOwnProperty("checkK")) this.current.checkK = temp.checkK;
					if(temp.delay && temp.delay<this.current.delay || !this.current.hasOwnProperty("delay")) this.current.delay = temp.delay;
					if(temp.repeat && temp.repeat>this.current.repeat || !this.current.hasOwnProperty("repeat")) this.current.repeat = temp.repeat;
					if(temp.locate && temp.locate!==this.current.locate || !this.current.hasOwnProperty("locate")) this.current.locate = 0;
					if(temp.logging && !this.current.logging) this.current.logging = 1;
					if(temp.Suspend) this.current.Suspend = this.suspendedOXPs.concat(temp.Suspend);
				}
			}
		}
	}
	if(this.current){
		if(this.current.Strength) missionVariables.CCL_OXPStrength = this.current.Strength; // Set mV for strength
		if(this.current.Suspend && this.current.Suspend.length){
			for(var s=0;s<this.current.Suspend.length;s++){
				c = this.current.Suspend[s];
				if(this.suspendedOXPs.indexOf(c)!==-1) continue;
				if(worldScripts[c] && worldScripts[c].addToCallbacks){
					worldScripts[c].addToCallbacks({worldScript:this.name,callBack:"suspendOXP"});
					this.suspendedOXPs.push(this.current.Suspend[s]);
				}
			}
		}
	} else {
		if(this.infoTimer){this.infoTimer.stop(); delete this.infoTimer;}
		if(this.repeatTimer){this.repeatTimer.stop(); delete this.repeatTimer;}
		if(this.suspendedOXPs.length){
			for(var s=0;s<this.suspendedOXPs.length;s++){
				c = this.suspendedOXPs[s];
				if(worldScripts[c] && worldScripts[c].removeFromCallbacks){
					worldScripts[c].removeFromCallbacks({worldScript:this.name,callBack:"suspendOXP"});
				}
			}
			this.suspendedOXPs = [];
		}
	}
	return;
};
// Set cleaning params
this.startCleaning = function(obj)
{
	if(!obj.checkC) return;
	else {
		this.checkOXPStrength.checkCoef = obj.checkC;
		if(obj.checkK) this.checkOXPStrength.checkKill = obj.checkK;
	}
	if(obj.logging) this.checkOXPStrength.logIt = obj.logging;
	// One shot timer
	if(!this.infoTimer){
		if(obj.delay) this.infoTimer = new Timer(this,this.checkAll,obj.delay);
		else this.infoTimer = new Timer(this,this.checkAll,10);
	}
	if(obj.locate) this.checkOXPStrength.checkLocation = obj.locate;
	// Repeat timer
	if(obj.repeat){
		if(!this.repeatTimer){
			if(obj.delay) this.repeatTimer = new Timer(this,this.checkAll,0,10+obj.delay);
			else this.repeatTimer = new Timer(this,this.checkAll,0,10);
		}
	}
	return;
};
this.checkAll = function()
{
	if(guiScreen==="GUI_SCREEN_LOAD"){
		if(this.infoTimer){this.infoTimer.stop(); delete this.infoTimer;}
		if(this.repeatTimer){this.repeatTimer.stop(); delete this.repeatTimer;}
		return;
	}
	this.checkOXPStrength.checkPower();
	if(this.infoTimer){this.infoTimer.stop(); delete this.infoTimer;}
	if(this.current.repeat) this.current.repeat--;
	else if(this.repeatTimer){this.repeatTimer.stop(); delete this.repeatTimer;}
	return;
};
// API
this.Cabal_Common_OXPStrengthAPI = function(){this._cclstrength = null;}
Cabal_Common_OXPStrengthAPI.prototype = {
	// Threshold distance to MainStation, WP or the lanes WP,WS,PS
	maxDist: 50000,
	// Threshold energy
	checkE: 800,
	// Threshold speed
	checkS: 500,
	// Threshold standard missiles
	checkM: 6,
	// Threshold escorts
	checkEsc: 4,
	// Threshold for putting in list
	checkCoef: 999999,
	// Threshold for killing
	checkKill: 999999,
	// Act only for location
	checkLocation: 0,
	logIt: false,
	weapons: ["EQ_WEAPON_PULSE_LASER",1,"EQ_WEAPON_BEAM_LASER",3,"EQ_WEAPON_MILITARY_LASER",5,"EQ_WEAPON_MINING_LASER",4,"EQ_WEAPON_THARGOID_LASER",8,"EQ_WEAPON_TWIN_PLASMA_CANNON",4],
	smissile: ["EQ_MISSILE",0,"EQ_HARDENED_MISSILE",0.6,"EQ_ARMOURY_STANDARD_MISSILE",0.1,"EQ_ARMOURY_HARD_MISSILE",0.7,"EQ_MILITARY_MISSILE",0.8,"EQ_RMB_CASCADE_MISSILE",0.6,
		"EQ_RMB_FRAG_MISSILE",0.4,"EQ_RMB_LAW_MISSILE",0.5,"EQ_RMB_THARGOID_MISSILE",0.1,"EQ_RMB_INTERCEPT_MISSILE",0.3,"EQ_RMB_OVERRIDE_MISSILE",0.3,"EQ_HARPOON_NUKE1_MISSILE",0.7,
		"EQ_HARPOON_ABM_MISSILE",0.5,"EQ_MANCHI_MISSILE",0.4,"EQ_VECTOR_FUNA_MISSILE",1.1,"EQ_VECTOR_FUNB_MISSILE",1.1,"EQ_VECTOR_FUNC_MISSILE",1.1,"EQ_KILLIT_MISSILE",2.0,
		"EQ_CT_MINE",1.2,"EQ_ECD_MINE",1.1],
	srole: ["sunskim-trader",0.6,"trader",0.6,"hunter",1.1,"police",1.1,"wingman",1.1,"interceptor",1.1,"pirate",1.1,"blackdog",1.1,"scavenger",0.3,"shuttle",0.2,"miner",0.2,
		"hermit-ship",0.4,"escort",0.9,"thargoid",0.9,"tharglet",0.9],
	storage: [],
	wayoff : [],
	checkSpaceLane: function(entity){
		if(system.isInterstellarSpace){
			if(this.checkLocation){
				var d = this.maxDist+1;
				return([0,d,entity.position.distanceTo(new Vector3D([0,0,0])),d,d,d]);
			} else return([-1,0,0,0,0,0]);
		}
		if(!system.sun || system.sun.isGoingNova || system.sun.hasGoneNova) return([1,0,0,0,0,0]);
		var distMS = entity.position.distanceTo(system.mainStation.position);
		var distWP = entity.position.distanceTo(new Vector3D([0,0,0]));
		var distLWP = Math.sqrt(entity.position.x*entity.position.x+entity.position.y*entity.position.y);
		var lanePS = new Vector3D(system.mainPlanet.position.subtract(system.sun.position));
		var SEV = new Vector3D(entity.position.subtract(system.sun.position));
		var distS = SEV.magnitude();
		var distLPS = distS*Math.sin(lanePS.angleTo(SEV));
		var laneWS = new Vector3D(new Vector3D([0,0,0]).subtract(system.sun.position));
		var distLWS = distS*Math.sin(laneWS.angleTo(SEV));
		if(distMS>this.maxDist && distWP>this.maxDist && distLWP>this.maxDist && distLPS>this.maxDist && distLWS>this.maxDist) return([2,Math.floor(distMS),Math.floor(distWP),Math.floor(distLWP),Math.floor(distLPS),Math.floor(distLWS)]);
		return([0,Math.floor(distMS),Math.floor(distWP),Math.floor(distLWP),Math.floor(distLPS),Math.floor(distLWS)]);
	},
	checkSubs: function(entity){
		var tur=0,wea=0,slots=0,w=0,wtype="",s=0,subwtype="",temp;
		if(entity.forwardWeapon){slots++; temp = this.weapons[this.weapons.indexOf(entity.forwardWeapon.equipmentKey)+1]; wtype += ""+temp; w += temp;}
		if(entity.aftWeapon){slots++; temp = this.weapons[this.weapons.indexOf(entity.aftWeapon.equipmentKey)+1]; wtype += ""+temp; w += temp;}
		if(entity.portWeapon){slots++; temp = this.weapons[this.weapons.indexOf(entity.portWeapon.equipmentKey)+1]; wtype += ""+temp; w += temp;}
		if(entity.starboardWeapon){slots++; temp = this.weapons[this.weapons.indexOf(entity.starboardWeapon.equipmentKey)+1]; wtype += ""+temp; w += temp;}
		if(entity.isPlayer && !slots){slots=4; w=32; wtype="0000";} // All null, treat as highest
		if(!entity.subEntityCapacity) return([slots,0,0,w,wtype,0,0]);
		for(var prop in entity.subEntities){
			if(entity.subEntities[prop].status==="STATUS_ACTIVE") tur += 4;
			if(entity.subEntities[prop].forwardWeapon){wea++; temp = this.weapons[this.weapons.indexOf(entity.subEntities[prop].forwardWeapon.equipmentKey)+1]; subwtype += ""+temp; s += temp;}
			if(entity.subEntities[prop].aftWeapon){wea++; temp = this.weapons[this.weapons.indexOf(entity.subEntities[prop].aftWeapon.equipmentKey)+1]; subwtype += ""+temp; s += temp;}
			if(entity.subEntities[prop].portWeapon){wea++; temp = this.weapons[this.weapons.indexOf(entity.subEntities[prop].portWeapon.equipmentKey)+1]; subwtype += ""+temp; s += temp;}
			if(entity.subEntities[prop].starboardWeapon){wea++; temp = this.weapons[this.weapons.indexOf(entity.subEntities[prop].starboardWeapon.equipmentKey)+1]; subwtype += ""+temp; s += temp;}
		}
		if(!tur && entity.subEntityCapacity>2 && entity.subEntities && entity.subEntities.length<3) tur = entity.subEntityCapacity*4; // no cheats
		return([slots,tur,wea,w,wtype,s,subwtype]);
	},
	checkMissiles: function(entity){
		if(!entity.missileCapacity) return(0);
		var slot,mtype=0;
		for(var prop in entity.missiles){
			if(entity.missiles[prop].equipmentKey){
				slot = this.smissile.indexOf(entity.missiles[prop].equipmentKey+1);
				if(slot===-1) mtype += 2;
				else mtype += slot;
			}
		}
		if(!mtype){
			if(entity.missileCapacity>this.checkM) return([1,entity.missileCapacity]);
			else return(0);
		}
		return([(1+mtype)*entity.missileCapacity,entity.missileCapacity]);
	},
	checkRole: function(entity){
		var r = entity.primaryRole,mul=1;
		var ind = this.srole.indexOf(r);
		if(ind!==-1) mul = this.srole[ind+1];
		return(mul);
	},
	checkPower: function(p){
		if(!player.ship.isValid) return(0);
		function isBad(entity){return(entity.isShip && entity.isValid && (!entity.isStation || (entity.isStation && !entity.isMainStation)) && entity.maxEnergy>this.checkE || entity.maxSpeed>this.checkS || this.checkSubs(entity) || this.checkMissiles(entity) || (entity.escorts && entity.escorts.length>this.checkEsc));}
		var ents,temp,en,sp,wep,def,coef,mis,esc,dist,rol,bomb=false;
		this.storage = [];
		if(p){
			if(p.isPlayer) ents = [player.ship];
			else if(p.isValid) ents =[p];
			else return(0);
		} else ents = system.filteredEntities(this,isBad,player.ship);
		if(ents.length){
			for(var i=0;i<ents.length;i++){
				if(!ents[i].name) continue;
				if(!p || (p && !p.isPlayer)){
					var exFlag=this.binContains(ents[i].name);
					if(exFlag) continue;
				}
				en = ents[i].maxEnergy;
				if(ents[i].isStation && !ents[i].maxSpeed) coef = Math.log(en);
				else {
					if(en>256) en += Math.sqrt(en);
					if(p && p.isPlayer){
						en += ents[i].maxAftShield*ents[i].aftShieldRechargeRate;
						en += ents[i].maxForwardShield*ents[i].forwardShieldRechargeRate;
					} else if(en>800) en *= 2;
					coef = Math.sqrt(en);
				}
				sp = ents[i].maxSpeed;
				if(ents[i].equipmentStatus("EQ_FUEL_INJECTION")==="EQUIPMENT_OK") sp *= 4;
				if(ents[i].isPiloted && !ents[i].isStation && ents[i].maxSpeed && ents[i].maxThrust>40) sp *= Math.log(ents[i].maxThrust);
				if(sp) coef += Math.sqrt(sp);
				else sp = 0;
				temp = this.checkSubs(ents[i]);
				if(typeof(temp)!=="number"){
					if(this.logIt) wep = "L:"+temp[0]+" T:"+temp[1]+" S:"+temp[2]+" "+temp[4]+","+temp[6];
					if(temp[0]) coef += Math.pow(1.4,temp[0])*Math.log(temp[3]); // Standard Laser
					if(temp[1]) coef += Math.min(Math.pow(1.4,temp[1])*2,0xfffff); // Turrets
					if(temp[2]) coef += Math.min(Math.pow(1.5,temp[2])*(2*Math.log(temp[5])),0xfffff); // Subent Laser
				} else if(this.logIt) wep = "L:0 T:0 S:0 -,-";
				temp = this.checkMissiles(ents[i]);
				if(temp[0]){
					coef += Math.pow(temp[1],1.7);
					mis = temp[1];
				} else mis = 0;
				if(ents[i].escorts && ents[i].escorts.length) esc = ents[i].escorts.length;
				else esc = 0;
				coef += Math.pow(esc,1.8);
				if(ents[i].isStation){
					def = ents[i].dockedDefenders;
				} else def = 0;
				coef += Math.pow(def,1.8);
				if(p && p.isPlayer){
					coef *= 1.5;
					if(missionVariables.targetAutolock==="TRUE") coef += 5;
					var eq = ["EQ_ECM","EQ_ENERGY_BOMB","EQ_TARGET_AUTOLOCK","EQ_ENERGY_UNIT","EQ_NAVAL_ENERGY_UNIT","EQ_CLOAKING_DEVICE","EQ_QC_MINE","EQ_SHIELD_BOOSTER","EQ_NAVAL_SHIELD_BOOSTER","EQ_MILITARY_JAMMER",
						"EQ_MILITARY_SCANNER_FILTER","EQ_SHIELD_ENHANCER","EQ_AMS","EQ_EEU","EQ_IRONHIDE_FORWARD","EQ_IRONHIDE_AFT","EQ_VORTEX_TURRET"];
					var eqi = eq.length;
					while(eqi--){if(EquipmentInfo.infoForKey(eq[eqi])!==null && player.ship.equipmentStatus(eq[eqi])==="EQUIPMENT_OK") coef += 5;}
					dist = "-";
					rol = 1;
				} else {
					dist = this.checkSpaceLane(ents[i]);
					if(dist[0]===0){
						if(this.checkLocation && dist[this.checkLocation]>this.maxDist) continue;
						if(dist[1]<this.maxDist) coef *= 1.5; // Main Station
						else if(dist[2]<this.maxDist) coef *= 1.5; // WP
						else if(dist[3]<this.maxDist) coef *= 1.3; // Lane WP
						else if(dist[4]<this.maxDist) coef *= 1.1; // Lane PS
						else if(dist[5]<this.maxDist) coef *= 1.2; // Lane WS
					}
					if(dist[0]===2) coef *= 1/Math.log(dist[3]-this.maxDist);
					rol = this.checkRole(ents[i]);
					coef *= rol;
				}
				var eq = ents[i].equipment,eqpoints=1;
				for(var eqq=0;eqq<eq.length;eqq++){
					var reg = eq[eqq].equipmentKey;
					var des = eq[eqq].description;
					if(reg.search(/TARGET/gi)!==-1) eqpoints *= 1.15;
					if(reg.search(/MISSILE/gi)!==-1) eqpoints *= 1.15;
					if(reg.search(/ENERGY/gi)!==-1) eqpoints *= 1.25;
					if(reg.search(/SHIELD/gi)!==-1) eqpoints *= 1.25;
					if(reg.search(/BOOST/gi)!==-1) eqpoints *= 1.4;
					if(reg.search(/NAVAL/gi)!==-1) eqpoints *= 1.4;
					if(reg.search(/TURRET/gi)!==-1) eqpoints *= 1.5;
					if(des.search(/ARMOUR/gi)!==-1) eqpoints *= 1.5;
					if(reg.search(/LASER/gi)!==-1 || des.search(/LASER/gi)!==-1) coef *= 2.5;
					if(reg.search(/LAZER/gi)!==-1 || des.search(/LAZER/gi)!==-1) coef *= 2.5;
				}
				coef += eqpoints;
				if(ents[i].equipmentStatus("EQ_CLOAKING_DEVICE")==="EQUIPMENT_OK") coef *= 1.3;
				if(ents[i].equipmentStatus("EQ_MILITARY_JAMMER")==="EQUIPMENT_OK") coef *= 1.5;
				if(ents[i].equipmentStatus("EQ_MILITARY_SCANNER_FILTER")==="EQUIPMENT_OK") coef *= 1.5;
				coef = Math.floor(coef);
				if(coef>this.checkCoef){
					if(this.logIt) this.storage.push("COEF:"+this.pad(coef,6)+" E:"+this.pad(en,7)+" : S:"+this.pad(sp,5)+" : C:"+ents[i].scanClass+" : "+ents[i].name+" ("+ents[i].primaryRole+") : W:"+wep+" : M:"+mis+" : Esc:"+esc+" : Def:"+def+" : dist:"+dist+" : roleMul:"+rol);
					if(!p && coef>this.checkKill && ents[i].isValid){
						if(ents[i].scriptInfo && ents[i].scriptInfo.ccl_missionShip==="true") continue;
						if(ents[i].script && ents[i].script.ccl_missionShip) continue;
						if(ents[i].AI && ents[i].AI==="cabal_common_leaveHotspotAI.plist") continue;
						if(ents[i].isMainStation || (ents[i].isStation && !ents[i].distanceTravelled)) continue;
						var custSc = false;
						if(!ents[i].script || (ents[i].script && (ents[i].script.hasOwnProperty("shipRemoved") || ents[i].script.hasOwnProperty("entityDestroyed")))) custSc = true;
						if(ents[i].status!=="STATUS_IN_FLIGHT") continue; // Avoid early calls resulting in undefined properties
						var rDist = ents[i].position.distanceTo(player.ship.position);
						if(rDist>Math.pow(ents[i].collisionRadius,2) && rDist>25600){
							if(ents[i].escorts && ents[i].escorts.length){
								var es = ents[i].escorts.length;
								while(es--){
									if(!ents[i].escorts[es].isValid) continue;
									if(!ents[i].escorts[es].script || ents[i].escorts[es].script.hasOwnProperty("shipRemoved") || ents[i].escorts[es].script.hasOwnProperty("entityDestroyed")) ents[i].escorts[es].remove(true);
									else ents[i].escorts[es].remove();
								}
							}
							if(custSc) ents[i].remove(true);
							else ents[i].remove();
							if(this.logIt) log("KILL","Kill: "+ents[i].name);
						} else {
							if(rDist>3000){
								if(this.logIt) log("KILL","Bomb: "+ents[i].name);
								if(!bomb){
									ents[i].spawn("cabal_common_oxps_mine",2);
									bomb = true;
								} else ents[i].spawn("cabal_common_oxps_minesub",2);
								if(custSc) ents[i].remove(true);
								else ents[i].remove();
							} else {
								if(ents[i].maxSpeed>100){
									ents[i].setScript("oolite-default-ship-script.js");
									ents[i].switchAI("cabal_common_leaveHotspotAI.plist");
									if(this.logIt) log("KILL","Switching Script/AI for: "+ents[i].name);
								} else if(this.logIt) log("KILL","Not possible. Player close to: "+ents[i].name);
							}
						}
					}
				}
			}
		}
		if(!p){
			if(this.logIt){
				if(system.isInterstellarSpace) log("Check","System: Interstellar.");
				else log("Check","System: "+system.name+" ID:"+system.ID+" Gov:"+system.government+" Eco:"+system.economy);
				log("Check","COEF=coefficient, E=energy, S=speed, C=scanClass, name(role), W=weapons (Lasers,Turrets,SubentLaser), M=missiles, Esc=escorts, Def=Defenders, dist=distance Station,WP,Lanes(WP,WS,PS), roleMul:multiplier.");
				var list = this.storage.sort();
				for(var i=0;i<list.length;i++){log("Check",list[i]);}
			}
		} else {
			var boo = player.ship.name.toLowerCase();
			if(this.wayoff.length){
				var patt1 = new RegExp(boo);
				for(var i=0;i<this.wayoff.length;i++){
					if(patt1.test(this.wayoff[i])) coef *= 2;
				}
			}
			if(this.logIt){
				if(p.isPlayer) log("Check","Playership checked in with coef: "+coef);
				else log("Check","Entity "+p.name+" checked in with coef: "+coef);
				log("Check","COEF=coefficient, E=energy, S=speed, C=scanClass, name(role), W=weapons (Lasers,Turrets,SubentLaser), M=missiles, Esc=escorts, Def=Defenders, dist=distance Station,WP,Lanes(WP,WS,PS), roleMul:multiplier.");
				log("Check",this.storage);
			}
			return(parseFloat(coef));
		}
		return(0);
	},
	pad: function(n,len){
		n = n.toFixed(0);
		var s = n.toString();
		if(s.length<len) s = ("0000000000"+s).slice(-len);
		return s;
	},
	arrRemoveByValue: function(arr,val){
		for(var i=0;i<arr.length;i++){
			if(arr[i]===val){
				arr.splice(i,1);
				break;
			}
		}
		return(arr);
	},
	binAdd: function(key,value){
		var node = {key: key,left: null,right: null, value: value},current;
		if(this._cclstrength === null) this._cclstrength = node;
		else {
			current = this._cclstrength;
			while(true){
				if(key<current.key){
					if(current.left===null){current.left = node; break;}
					else current = current.left;
				} else if(key>current.key){
					if(current.right===null){current.right = node; break;}
					else current = current.right;
				} else break;
			}
		}
		return;
	},
	binContains: function(key){
		var found = false,current = this._cclstrength;
		while(!found && current){
			if(key<current.key) current = current.left;
			else if(key>current.key) current = current.right;
			else found = true;
		}
		if(!found) return (false);
		return (current.value);
	}
};
Scripts/Cabal_Common_Overlay.js
"use strict";
this.name = "Cabal_Common_Overlay";
this.author = "Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Overlay handling";
this.version = "1.7";

this.startUp = function()
{
	delete this.startUp;
	this.ovList = [[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0]];
	this.ovListObj = [[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0]];
	this.ovInstrumentPos = [[8,8,9],[6,8,9],[4,8,9],[2,8,9],[0,8,9],[-2,8,9],[-4,8,9],[-6,8,9],[-8,8,9]];
	this.ovCharacterPos = [[8,5,9],[8,3,9],[8,1,9],[8,-1,9],[8,-3,9],[-8,5,9],[-8,3,9],[-8,1,9],[-8,-1,9],[-8,-3,9]];
	this.ovDeadSpotPos = [[0,0,1]];
	this.ovPredicate = null;
	if(oolite.gameSettings.shaderEffectsLevel==='SHADERS_OFF' || oolite.gameSettings.shaderEffectsLevel==='SHADERS_NOT_SUPPORTED') this.ovShadersOff = true;
};
this.shipWillLaunchFromStation = this.shipWillExitWitchspace = function()
{
	this.doAdd();
};
this.shipLaunchedFromStation = this.shipExitedWitchspace = function()
{
	this.cleanupTimer = new Timer(this,this.doCleanup,0,2);
};
this.shipWillDockWithStation = this.shipWillEnterWitchspace = function()
{
	if(this.cleanupTimer){this.cleanupTimer.stop(); delete this.cleanupTimer;}
	this.ovRemoveAll();
};
this.doCleanup = function()
{
	for(var c=0;c<3;c++){
		for(var cc=0;cc<this.ovList[c].length;cc++){
			if(!this.ovList[c][cc].isValid){
				this.ovList[c][cc] = 0;
				this.ovListObj[c][cc] = 0;
			}
		}
	}
	return;
};
this.doAdd = function()
{
	for(var o=0;o<3;o++){
		for(var i=0;i<this.ovListObj[o].length;i++) if(this.ovListObj[o][i]) this.ovAdd(this.ovListObj[o][i],1);
	}
	for(var c=0;c<3;c++){
		for(var cc=0;cc<this.ovList[c].length;cc++) if(!this.ovList[c][cc]) this.ovListObj[c][cc] = 0;
	}
	return;
};
// API
/* worldScripts.Cabal_Common_Overlay.ovAdd(obj,off)
	obj		Object. see below
	off		Boolean. If true overlay is added, but offscreen
	
	obj:
	cclov_id			String. Identifier.
	cclov_type			Number. 0=Notification, 1=Character, 2=Fullscreen
	cclov_autoremove	Boolean. Removes effect after cclov_blend seconds
	cclov_blend			Number. Duration for effect.
	cclov_fx			Entity. VisualEffect to be used instead.
	cclov_png			Texture. Must be specified if cclov_fx is not used.
	cclov_color			Array. RGB values in range 0...3 for character overlays.
	
	Returns:
		Visual Effect (when docked true) or false */
this.ovAdd = function(obj,off){
	if(!obj || typeof(obj.cclov_type)!=='number' || !obj.cclov_id) return(false);
	if(!obj.whom) obj.whom = player.ship;
	if(!obj.whom.isValid) return(false);
	if(!obj.cclov_type) obj.cclov_type = 0;
	var where = obj.whom.position,ind = -1,result,f,ents,lookup="ovList";
	// Check if exists already
	this.ovPredicate = obj.cclov_id;
	if(player.ship.docked){
		ents = this.ovFindDocked(0).concat(this.ovFindDocked(1));
		lookup = "ovListObj";
	} else ents = this.ovFind(0).concat(this.ovFind(1));
	if(ents.length) return(false);
	for(var i=0;i<this[lookup][obj.cclov_type].length;i++){
		if(this[lookup][obj.cclov_type][i]===0){
			ind = i;
			break;
		}
	}
	if(ind!==-1){
		if(player.ship.docked){
			this.ovListObj[obj.cclov_type][ind] = obj; // Store
			return(true);
		} else {
			if(obj.cclov_fx){
				f = system.addVisualEffect(obj.cclov_fx,where);
			} else {
				if(obj.cclov_color) f = system.addVisualEffect("ccl_overlay_color",where);
				else f = system.addVisualEffect("ccl_overlay",where);
			}
			if(f){
				if(obj.cclov_png) this.ovTexChange(f,obj.cclov_png);
				f.script.cclov_tar = obj.whom;
				if(this.ovShadersOff) f.script.cclov_noShade = true;
				this.ovList[obj.cclov_type][ind] = f;
				switch(obj.cclov_type){
					case 0: result = this.ovInstrumentPos[ind]; break;
					case 1: result = this.ovCharacterPos[ind]; break;
					case 2: result = this.ovDeadSpotPos[ind]; break;
				}
				f.shaderVector1 = result;
				if(obj.cclov_blend && obj.cclov_blend>0){
					if(obj.cclov_type===0) f.script.cclov_blendI = clock.absoluteSeconds+obj.cclov_blend;
					if(obj.cclov_type===1) f.script.cclov_blendC = clock.absoluteSeconds+obj.cclov_blend;
					if(obj.cclov_type===2) f.script.cclov_blendA = clock.absoluteSeconds+obj.cclov_blend;
				}
				f.script.cclov_id = obj.cclov_id;
				f.script.cclov_type = obj.cclov_type;
				this.ovListObj[obj.cclov_type][ind] = obj; // Store
				if(off && obj.cclov_blend!==-1){
					if(obj.cclov_type===0) f.shaderInt2 = 20;
					if(obj.cclov_type===1) f.shaderInt1 = 20;
				}
				if(obj.cclov_autoremove) f.script.cclov_autoremove = true;
				if(obj.cclov_type===1){
					if(obj.cclov_color) f.shaderVector2 = obj.cclov_color;
				}
				return(f);
			}
		}
	}
	return(false);
};
this.ovTexChange = function(what,cclov_png){
	var mat = what.getMaterials();
	var k = Object.keys(mat);
	if(typeof(mat[k[0]].textures[0])==='string') mat[k[0]].textures[0] = cclov_png;
	else mat[k[0]].textures[0].name = cclov_png;
	mat[k[0]].emission_map = cclov_png;
	mat[k[0]].ambient_color = [0,0,0,0];
	mat[k[0]].diffuse_color = [0,0,0,0];
	mat[k[0]].shininess = 0;
	what.setMaterials(mat);
	return(true);
};
this.ovSearch = function(element){
	return(element.isVisualEffect && element.script.cclov_id===this.ovPredicate);
};
this.ovFind = function(cclov_type,who){
	if(who) this.ovPredicate = who;
	return(this.ovList[cclov_type].filter(this.ovSearch,this));
};
this.ovFindDocked = function(cclov_type,who){
	if(who) this.ovPredicate = who;
	return(this.ovListObj[cclov_type].filter(this.ovSearch,this));
};
/* worldScripts.Cabal_Common_Overlay.ovSpeak(who,cclov_blend,message,whom,autoremove)
	who				String. Identifier.
	cclov_blend		Number. Duration for effect.
	message			String.
	whom			Entity.
	autoremove		Boolean. Removes effect after cclov_blend seconds. */
this.ovSpeak = function(who,cclov_blend,message,whom,autoremove){
	if(!who || !cclov_blend || !player.ship.isValid || player.ship.docked) return(false);
	this.ovPredicate = who;
	var ents = this.ovFind(0).concat(this.ovFind(1));
	if(!ents.length) return(false);
	var sc = ents[0].script;
	ents[0].shaderFloat2 = cclov_blend;
	if(message){
		if(whom){
			if(whom.isValid && whom.isShip) whom.commsMessage(message);
		} else {
			if(sc.cclov_type===1) player.commsMessage(who+": "+message,cclov_blend);
			else player.consoleMessage(who+": "+message,cclov_blend);
		}
		var snd = new SoundSource();
		snd.sound = "[cabal_common_commbeep]";
		snd.play();
	}
	if(cclov_blend){
		if(sc.cclov_type===0 && sc.cclov_blendI!==-1) sc.cclov_blendI = clock.absoluteSeconds+cclov_blend;
		if(sc.cclov_type===1 && sc.cclov_blendC!==-1) sc.cclov_blendC = clock.absoluteSeconds+cclov_blend;
	}
	if(sc.valueUpdate) sc.valueUpdate();
	if(autoremove) sc.cclov_autoremove = true;
	return(true);
};
this.ovLog = function(){
	for(var o=0;o<3;o++){
		for(var i=0;i<this.ovList[o].length;i++) if(this.ovList[o][i]) log("CCL_FX","List: cclov_type:"+o+" index:"+i+" : "+this.ovList[o][i].script.cclov_id);
		for(var i=0;i<this.ovListObj[o].length;i++) if(this.ovListObj[o][i]) for(var prop in this.ovListObj[o][i]) log("CCL_FX",prop+" : "+this.ovListObj[o][i][prop]);
	}
	return;
};
/* worldScripts.Cabal_Common_Overlay.ovRemove(who)
	who				String. Identifier. */
this.ovRemove = function(who){
	if(!player.ship.isValid) return(false);
	var ind = -1, li = 0,lookup = "ovList";
	if(player.ship.docked){
		lookup = "ovListObj";
		for(var i=0;i<this[lookup][0].length;i++) if(this[lookup][0][i] && this[lookup][0][i].cclov_id===who){
			ind = i; li = 0; break;
		}
		if(ind===-1){
			for(var i=0;i<this[lookup][1].length;i++) if(this[lookup][1][i] && this[lookup][1][i].cclov_id===who){
				ind = i; li = 1; break;
			}
		}
	} else {
		for(var i=0;i<this[lookup][0].length;i++) if(this[lookup][0][i] && this[lookup][0][i].script.cclov_id===who){
			ind = i; li = 0; break;
		}
		if(ind===-1){
			for(var i=0;i<this[lookup][1].length;i++) if(this[lookup][1][i] && this[lookup][1][i].script.cclov_id===who){
				ind = i; li = 1; break;
			}
		}
	}
	if(ind===-1) return(false);
	if(!player.ship.docked){
		this.ovList[li][ind].remove();
		this.ovList[li][ind] = 0;
	}
	this.ovListObj[li][ind] = 0;
	return(true);
};
this.ovRemoveAll = function(){
	var ar = 0;
	for(var o=0;o<3;o++){
		for(var i=0;i<this.ovList[o].length;i++){
			ar = 0;
			if(this.ovList[o][i]){
				if(!this.ovList[o][i].isValid){
					this.ovListObj[o][i] = 0;
					this.ovList[o][i] = 0;
				} else {
					if(this.ovList[o][i].script.cclov_autoremove) ar = 1;
					this.ovList[o][i].remove();
					this.ovList[o][i] = 0;
					if(ar) this.ovListObj[o][i] = 0;
				}
			}
		}
	}
	return(true);
};
Scripts/Cabal_Common_SpecialMarkets.js
"use strict";
this.name = "Cabal_Common_SpecialMarkets";
this.author = "Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Additional Market for special goods";
this.version = "1.7";

this.registerLocation = function(obj)
{
	if(this.startUp || !obj || !Object.isExtensible(obj)) return(0);
	var req = ["item","galaxy","buyID","sellID","cb","cbb","priceclass","refresh","desc","stationID"];
	var typ = ["string","object","object","object","string","string","number","number","string","object"];
	var ctyp = ["gemStones","gold","platinum","alienItems","alloys","computers","firearms","food","furs","liquorWines","luxuries","machinery","minerals","narcotics","radioactives","slaves","textiles"];
	for(var i=0;i<req.length;i++) if(!obj.hasOwnProperty(req[i]) || typeof(obj[req[i]])!=typ[i]) return(0);
	for(var id=0;id<obj.buyID.length;id++) if(obj.sellID.indexOf(obj.buyID[id])!==-1) return(0);
	var item,galaxy,buyID,sellID,stationID,cb,cbb,priceclass,refresh,desc,block,contract,contractUnit,contractDesc,offset,avail,times,decrease,blackFriday,legal,model,oneShot,blend;
	item = obj.item.substr(0,40);
	galaxy = obj.galaxy;
	buyID = obj.buyID;
	sellID = obj.sellID;
	stationID = obj.stationID;
	cb = obj.cb;
	cbb = obj.cbb;
	desc = obj.desc;
	priceclass = this.helper.clamp(obj.priceclass,100,5);
	refresh = this.helper.clamp(obj.refresh,1,0.06);
	if(!obj.hasOwnProperty("block") || typeof(obj.block)!=="boolean") block=false;
	else block = true;
	if(block || !obj.hasOwnProperty("contract") || typeof(obj.contract)!=="string") contract=false;
	else contract=true;
	if(contract){
		var c = ctyp.indexOf(obj.contract);
		if(c===-1) contract=false;
		else {
			if(!c) contractUnit = "g";
			else if(c===1 || c===2) contractUnit = "kg";
			else contractUnit = "t";
			contractDesc = displayNameForCommodity(ctyp[c]);
			stationID=[["main"],["main"]];
		}
	}
	if(!obj.hasOwnProperty("offset") || typeof(obj.offset)!=="number") offset=1.3;
	else offset = this.helper.clamp(obj.offset,5,1.3);
	if(!obj.hasOwnProperty("avail") || typeof(obj.avail)!=="number") avail=0;
	else avail = this.helper.clamp(obj.avail,25,0);
	if(!obj.hasOwnProperty("times") || typeof(obj.times)!=="number") times=clock.days;
	else times = this.helper.clamp(obj.times,clock.days,clock.days-900);
	if(!obj.hasOwnProperty("decrease") || typeof(obj.decrease)!=="number") decrease=0.1;
	else decrease = this.helper.clamp(obj.decrease,0.5,0.1);
	if(!obj.hasOwnProperty("blackFriday") || typeof(obj.blackFriday)!=="number") blackFriday=0;
	else blackFriday = this.helper.clamp(obj.blackFriday,0.2,0);
	if(!obj.hasOwnProperty("legal") || typeof(obj.legal)!=="boolean") legal=true;
	else legal=false;
	if(!obj.hasOwnProperty("model") || typeof(obj.model)!=="string") model="cargopod";
	else model = obj.model;
	if(!obj.hasOwnProperty("oneShot") || typeof(obj.oneShot)!=="boolean") oneShot=null;
	else oneShot = obj.oneShot;
	for(var goc=0;goc<galaxy.length;goc++){
		for(var loc=0;loc<buyID.length;loc++){
			if(this.hold){
				for(var o=0;o<8;o++){
					for(var i=0;i<this.hold[o].length;i++){
						if(item===this.hold[o][i].item && buyID[loc]===this.hold[o][i].buyID){
							times = this.hold[o][i].times;
							if(this.hold[o][i].hasOwnProperty("blend")) blend = this.hold[o][i].blend;
							if(this.hold[o][i].hasOwnProperty("hold") && !legal){
								this.inspection++;
								this.rockOn.push(item);
							}
							break;
						}
					}
				}
			}
			var place = {item:item,galaxy:galaxy[goc],buyID:buyID[loc],sellID:sellID,stationID:stationID,cb:cb,cbb:cbb,priceclass:priceclass,refresh:refresh,
				desc:desc,block:block,contract:contract,contractUnit:contractUnit,contractDesc:contractDesc,offset:offset,avail:avail,times:times,
				decrease:decrease,blackFriday:blackFriday,legal:legal,model:model,oneShot:oneShot,blend:blend};
			this.marketLocations[galaxy[goc]].push(place);
		}
	}
	return(1);
};
// Removes item from lists
this.removeTradeItem = function(item,immediate)
{
	if(!immediate && this.delayRemove && player.ship.docked) this.delayRemove.push(item);
	else {
		for(var o=0;o<8;o++){
			for(var i=0;i<this.marketLocations[o].length;i++){
				if(item===this.marketLocations[o][i].item){
					this.marketLocations[o] = this.helper.arrRemoveByValue(this.marketLocations[o],this.marketLocations[o][i]);
					--i;
				}
			}
		}
	}
	return;
};
// Check players hold and return amount
this.checkPSHold = function(item)
{
	if(this.psHold.length){
		for(var i=0;i<this.psHold.length;i++){
			if(this.psHold[i].item===item) return(this.psHold[i].store);
		}
	}
	return(0);
};
this.cleanHold = function()
{
	if(this.psHold.length){
		for(var i=0;i<this.psHold.length;i++){
			if(!this.psHold[i].stored){
				this.psHold = this.helper.arrRemoveByValue(this.psHold,this.psHold[i]);
				--i;
			}
		}
	}
	return;
};
this.startUp = function()
{
	delete this.startUp;
	this.helper = new worldScripts.Cabal_Common_Functions.Cabal_Common();
	this.marketLocations = [[],[],[],[],[],[],[],[]];
	this.rockOn = [];
	this.psHold = [];
	this.holdPage = 0;
	this.prep(1);
	this.mso = false;
	this.inspection = 0;
	if(missionVariables.ccl_markets){
		this.hold = JSON.parse(missionVariables.ccl_markets);
		for(var o=0;o<8;o++){
			for(var i=0;i<this.hold[o].length;i++){
				if(this.hold[o][i].hasOwnProperty("hold")) this.psHold.push({item:this.hold[o][i].item,stored:this.hold[o][i].hold});
			}
		}
		missionVariables.ccl_markets = null;
	}
};
this.playerWillSaveGame = function()
{
	this.shipWillLaunchFromStation();
	var data = [[],[],[],[],[],[],[],[]],temp,check,ind;
	for(var o=0;o<8;o++){
		for(var i=0;i<this.marketLocations[o].length;i++){
			this.curItem = this.marketLocations[o][i];
			temp = {item:this.curItem.item,buyID:this.curItem.buyID,times:this.curItem.times};
			check = this.psHold.filter(this.ccls_findPSHold,this);
			if(!check.length) continue;
			ind = this.psHold.indexOf(check[0]);
			if(ind!==-1) temp.hold = check[0].stored;
			if(this.curItem.hasOwnProperty("blend")) temp.blend = this.curItem.blend;
			data[o].push(temp);
		}
	}
	missionVariables.ccl_markets = JSON.stringify(data);
	this.saved = true;
};
this.guiScreenChanged = function()
{
	if(!player.ship.docked) return;
	if(this.saved){
		missionVariables.ccl_markets = null;
		delete this.saved;
	}
};
this.shipWillExitWitchspace = function()
{
	this.prep(1);
};
this.guiScreenWillChange = function(to)
{
	if(to!=="GUI_SCREEN_EQUIP_SHIP") return;
	var buys = this.marketLocations[galaxyNumber].filter(this.ccls_findBuy,this);
	var sells = this.marketLocations[galaxyNumber].filter(this.ccls_findSell,this);
	var temp;
	sells = this.helper.arrSortByProperty(sells,"times");
	for(var w=0;w<sells.length;w++){
		if(temp && temp===sells[w].item){
			sells = this.helper.arrRemoveByValue(sells,sells[w]);
			--w;
		} else temp = sells[w].item;
	}
	this.trades = buys.concat(sells);
	if(this.trades.length){
		this.prep();
		EquipmentInfo.infoForKey("EQ_CABAL_COMMON_SPECIALMARKETS").effectiveTechLevel = 1;
	} else missionVariables.TL_FOR_EQ_CABAL_COMMON_SPECIALMARKETS = null;
};
this.prep = function(n)
{
	var temp = 0;
	if(!n) temp = this.store.rnd
	this.store = {diffTime:0,avail:0,totUnits:0,totBuy:0,totSell:0};
	this.snd = new SoundSource();
	if(n) this.store.rnd=this.helper.clamp(Math.random(),0.81,0.6);
	else this.store.rnd = temp;
	return;
};
this.ccls_findBuy = function(element){return(element.buyID===system.ID && ((player.ship.dockedStation.isMainStation && element.stationID[0].indexOf("main")!==-1) || (!player.ship.dockedStation.isMainStation && element.stationID[0].indexOf(player.ship.dockedStation.name)!==-1)));};
this.ccls_findSell = function(element){return(!element.contract && element.sellID.indexOf(system.ID)!==-1 && ((player.ship.dockedStation.isMainStation && element.stationID[1].indexOf("main")!==-1) || (!player.ship.dockedStation.isMainStation && element.stationID[1].indexOf(player.ship.dockedStation.name)!==-1)));};
this.ccls_findPSHold = function(element){return(element.item==this.curItem.item);};
this.shipWillLaunchFromStation = function()
{
	if(this.delayRemove){
		for(var i=0;i<this.delayRemove.length;i++) this.removeTradeItem(this.delayRemove[i],1);
		delete this.delayRemove;
	}
	missionVariables.TL_FOR_EQ_CABAL_COMMON_SPECIALMARKETS = null;
	delete this.curItem;
	delete this.curIndex;
	delete this.curPSHold;
	delete this.scr;
	delete this.snd;
	delete this.buy;
	this.mso = false;
	this.cleanHold();
};
this.shipWillEnterWitchspace = function()
{
	delete this.store;
};
this.playerBoughtEquipment = function(equipmentKey)
{
	if(equipmentKey==="EQ_CABAL_COMMON_SPECIALMARKETS"){
		player.ship.removeEquipment("EQ_CABAL_COMMON_SPECIALMARKETS");
		if(!this.delayRemove) this.delayRemove = [];
		this.cycleScreens();
	}
	return;
};
this.cycleScreens = function(){worldScripts.Cabal_Common_SpecialMarkets.cycler(); return;};
this.cycler = function()
{
	if(typeof(this.scr)==="undefined") this.scr=0;
	var chc = 0,txt = "",temp,l,blendOut=false;
	this.curItem = this.trades[this.scr];
	if(this.curItem.hasOwnProperty("blend") && clock.days<this.curItem.blend) blendOut=true;
	if(this.trades.length>1) chc += 4;
	if(this.curItem.buyID===system.ID) chc += 1;
	if(this.curItem.sellID.indexOf(system.ID)!==-1) chc += 2;
	if(this.curItem.model) this.showScreen("CABAL_COMMON_SPECIALMARKET_"+chc,this.curItem.model);
	else this.showScreen("CABAL_COMMON_SPECIALMARKET_"+chc,null);
	txt += this.curItem.item+(this.curItem.contract?" ("+this.curItem.contractDesc+")":"")+"\n-----------------------------------------------------\n";
	if(this.curItem.contract){
		this.store.tarSys = this.curItem.sellID[Math.floor(Math.random()*this.curItem.sellID.length)];
		this.store.tarSysName = System.systemNameForID(this.store.tarSys);
	}
	this.store.diffTime = this.helper.clamp(clock.days-this.curItem.times,1000,0);
	var dec = Math.log((1+this.curItem.decrease*this.store.diffTime)+(this.curItem.avail?Math.log(this.curItem.avail):0));
	var prod = this.curItem.refresh*this.store.diffTime;
	var prodTot = prod/(dec>1?1/dec:1);
	var us = (this.store.diffTime?(this.curItem.avail+(prod?prod:0.1)/(prodTot?prodTot:1)*(this.store.diffTime-dec)/(dec>1?dec:1))/(this.curItem.offset>1?this.curItem.offset:1.3)/Math.log(this.curItem.priceclass*0.3)*this.curItem.refresh:0);
	this.store.totUnits = Math.floor(us);
	if((chc&1) && blendOut) this.store.totUnits = 0;
	var pricernd = Math.sqrt(this.curItem.priceclass*this.store.rnd);
	this.store.totBuy = (((Math.random()>0,5?this.curItem.priceclass-pricernd:this.curItem.priceclass+pricernd)/(Math.log(us+3)*this.store.rnd)/(dec>1?dec:1))/this.curItem.decrease*(this.curItem.refresh?1/this.curItem.refresh:1))/Math.sqrt(this.curItem.offset);
	this.store.totBuy = parseFloat(this.store.totBuy.toFixed(1));
	this.store.totSell = this.store.totBuy*(this.curItem.offset-(1/(1/this.curItem.decrease)*this.store.rnd));
	this.store.totSell = parseFloat(this.store.totSell.toFixed(1));
	if(chc&1){
		temp=this.store.totUnits+" "+(this.curItem.block?"t":(this.curItem.contract?this.curItem.contractUnit:"g"))+"\n";
		l=defaultFont.measureString(temp);
		txt += this.helper.strToWidth("Units:",14-l," ")+temp;
		temp=global.formatCredits(this.store.totBuy,1,1)+"\n";
		l=defaultFont.measureString(temp);
		txt += this.helper.strToWidth("Buy:",14-l," ")+temp;
		temp=global.formatCredits(0,1,1)+"\n";
		l=defaultFont.measureString(temp);
		txt += this.helper.strToWidth("Sell:",14-l," ")+temp;
	} else {
		temp=this.store.totUnits+" "+(this.curItem.block?"t":(this.curItem.contract?this.curItem.contractUnit:"g"))+"\n";
		l=defaultFont.measureString(temp);
		txt += this.helper.strToWidth("Units:",14-l," ")+temp;
		temp=global.formatCredits(0,1,1)+"\n";
		l=defaultFont.measureString(temp);
		txt += this.helper.strToWidth("Buy:",14-l," ")+temp;
		temp=global.formatCredits(this.store.totSell,1,1)+"\n";
		l=defaultFont.measureString(temp);
		txt += this.helper.strToWidth("Sell:",14-l," ")+temp;
	}
	this.curPSHold = this.psHold.filter(this.ccls_findPSHold,this);
	this.curIndex = this.psHold.indexOf(this.curPSHold[0]);
	if(this.curPSHold.length) temp=this.curPSHold[0].stored+" "+(this.curItem.block?"t":(this.curItem.contract?this.curItem.contractUnit:"g"))+" / ("+player.ship.cargoSpaceAvailable+")\n";
	else temp="0 "+(this.curItem.block?"t":(this.curItem.contract?this.curItem.contractUnit:"g"))+" / ("+player.ship.cargoSpaceAvailable+")\n";
	if((chc&1) && player.ship.specialCargo) temp="- SEALED -\n";
	l=defaultFont.measureString(temp);
	txt += this.helper.strToWidth("Cargohold:",14-l," ")+temp;
	txt += "-----------------------------------------------------\n";
	if((chc&1)){
		txt += "Cost for all available:\f("+global.formatCredits(this.store.totUnits*this.store.totBuy,1,1)+")\n";
		txt += "Estimated TradeIn:\f\f("+global.formatCredits(this.store.totUnits*this.store.totSell,1,1)+")\n";
	} else {
		txt += "\n";
		if(this.curPSHold.length) txt += "Estimated TradeIn:\f\f("+global.formatCredits(this.curPSHold[0].stored*this.store.totSell,1,1)+")\n";
		else txt += "Estimated TradeIn:\f\f("+global.formatCredits(0,1,1)+")\n";
	}
	if(this.curItem.contract){
		var t = System.infoForSystem(galaxyNumber,system.ID).distanceToSystem(System.infoForSystem(galaxyNumber,this.store.tarSys));
		txt +=this.helper.strToWidth("Credits available:\f\f  ("+global.formatCredits(player.credits,1,1)+")",20," ");
		if(this.store.tarSys!==system.ID) txt += this.store.tarSysName+", "+t.toFixed(2)+"LY, "+this.helper.mapCoordsDirection(this.store.tarSys,system.ID)+"\n";
		else txt += "\n";
	} else txt += "Credits available:\f\f  ("+global.formatCredits(player.credits,1,1)+")\n";
	txt += "\nDescription:\n"+this.curItem.desc.substr(0,350);
	mission.addMessageText(txt);
	delete this.buy;
	this.mso = false;
	return;
};
this.showScreen = function(chc,model)
{
	var spin = null,ov="cabal_common_specialmarkets_ov.png";
	if(typeof(model)!=="undefined") spin = true;
	if(this.curItem.block){
		if(this.curItem.buyID===system.ID) ov="cabal_common_specialmarkets_ovSeal.png";
		else ov="cabal_common_specialmarkets_ovUnseal.png";
	} else if(this.curItem.contract) ov="cabal_common_specialmarkets_ovContract.png";
	mission.runScreen({title:system.name+" Special Market",background:"cabal_common_specialmarkets.png",overlay:ov,choicesKey:chc,music:"cabal_common_specialmarkets.ogg",model:model,spinModel:spin},this.choiceEval);
	if(model) this.helper.entScreenCornerPos(0.8,0.9,1.1,3.2);
	return;
};
this.choiceEval = function(choice){worldScripts.Cabal_Common_SpecialMarkets.choiceEvaluation(choice); return;};
this.choiceEvaluation = function(choice)
{
	if(!choice) return;
	switch(choice){
		case "CABAL_COMMON_SPECIALMARKETA": this.scr++; if(this.scr>=this.trades.length) this.scr=0; this.cycleScreens(); break;
		case "CABAL_COMMON_SPECIALMARKETBA":
			if(!player.ship.cargoSpaceAvailable || player.ship.cargoSpaceAvailable<this.store.totUnits){
				this.snd.sound = "ccl_markets_fail.ogg";
			} else if(player.credits>=this.store.totUnits*this.store.totBuy){
				if(!this.store.totUnits){
					this.snd.sound = "ccl_markets_nounits.ogg";
				} else {
					if(!this.curItem.contract){
						if(this.curItem.block && player.ship.cargoSpaceAvailable<player.ship.cargoSpaceCapacity){
							this.snd.sound = "ccl_markets_fail.ogg";
							this.snd.play();
							this.cycleScreens();
							return;
						}
						if(this.curIndex===-1) this.psHold.push({item:this.curItem.item,stored:this.store.totUnits});
						else this.psHold[this.curIndex].stored += this.store.totUnits;
					}
					player.credits -= this.store.totUnits*this.store.totBuy;
					this.trades[this.scr].blend = clock.days+Math.ceil(Math.log(this.curItem.priceclass));
					if(!this.curItem.legal){
						this.inspection++;
						this.rockOn.push(this.curItem.item);
					}
					if(this.curItem.block) player.ship.useSpecialCargo(this.curItem.item);
					else if(this.curItem.contract){
						var t = System.infoForSystem(galaxyNumber,system.ID).distanceToSystem(System.infoForSystem(galaxyNumber,this.store.tarSys))*1.3;
						player.ship.awardContract(this.store.totUnits,this.curItem.contractDesc,system.ID,this.store.tarSys,clock.seconds+t*24*3600,this.store.totUnits*this.store.totSell)
					}
					this.snd.sound = "ccl_markets_bought.ogg";
				}
			} else this.snd.sound = "ccl_markets_creditsfail.ogg";
			this.snd.play();
			this.cycleScreens();
			break;
		case "CABAL_COMMON_SPECIALMARKETBN":
			if(!player.ship.cargoSpaceAvailable || player.ship.cargoSpaceAvailable<this.store.totUnits){
				this.snd.sound = "ccl_markets_fail.ogg";
				this.snd.play();
				this.cycleScreens();
			} else if(!this.store.totUnits){
				this.snd.sound = "ccl_markets_nounits.ogg";
				this.snd.play();
				this.cycleScreens();
			} else {
				worldScripts.Cabal_Common_Keyboard.start(this.name,1,2);
				this.mso = true;
				this.buy = true;
			}
			break;
		case "CABAL_COMMON_SPECIALMARKETSA":
			if(this.curIndex!==-1){
				if(this.curPSHold.length && this.psHold[this.curIndex].stored){
					if(worldScripts[this.trades[this.scr].cb] && worldScripts[this.trades[this.scr].cb][this.trades[this.scr].cbb]){
						if(this.curItem.buyID===system.ID) this.trades[this.scr].avail += this.psHold[this.curIndex].stored;
						player.credits += this.psHold[this.curIndex].stored*this.store.totSell;
						var cr = this.psHold[this.curIndex].stored;
						this.psHold[this.curIndex].stored = 0;
						if(this.curItem.block && player.ship.specialCargo===this.curItem.item) player.ship.removeAllCargo();
						var check = worldScripts[this.trades[this.scr].cb][this.trades[this.scr].cbb](this.psHold[this.curIndex].item,cr);
						if(!this.curItem.legal){
							this.inspection--;
							if(this.inspection<0) this.inspection=0;
							this.rockOn = this.helper.arrRemoveByValue(this.rockOn,this.curItem.item);
						}
						if(this.curItem.oneShot) this.removeTradeItem(this.curItem.item);
						if(check) this.snd.sound = "ccl_markets_complete.ogg";
						else {
							this.snd.sound = "ccl_markets_sold.ogg";
							this.snd.play();
							this.mso = true;
							return; // OXPs displays own screen
						}
					} else this.snd.sound = "ccl_markets_fail.ogg";
				} else this.snd.sound = "ccl_markets_nounits.ogg";
			} else this.snd.sound = "ccl_markets_nounits.ogg";
			this.snd.play();
			this.cycleScreens();
			break;
		case "CABAL_COMMON_SPECIALMARKETSN":
			if(this.curPSHold.length || this.curIndex===-1 || !this.psHold[this.curIndex].stored){
				this.snd.sound = "ccl_markets_nounits.ogg";
				this.snd.play();
				this.cycleScreens();
			} else {
				worldScripts.Cabal_Common_Keyboard.start(this.name,1,2);
				this.mso = true;
				this.buy = false;
			}
			break;
		case "CABAL_COMMON_SPECIALMARKETP":
			this.holdPage++;
			if(this.holdPage*10>this.psHold.length-1) this.holdPage = 0;
			var a = this.holdPage*10,chc;
			if(a>this.psHold.length-1) a = 0;
			chc = "CABAL_COMMON_SPECIALMARKET_OVERVIEW_3";
			var b = this.helper.clamp(this.psHold.length,99,0);
			b = Math.min(b,a+10);
			mission.runScreen({title:"Loaded Cargo",background:"cabal_common_specialmarkets.png",overlay:"cabal_common_specialmarkets_ov.png",choicesKey:chc,music:"cabal_common_specialmarkets.ogg"},this.choiceEval);
			for(var c=a;c<b;c++) mission.addMessageText(this.helper.strToWidth(this.psHold[c].item,14)+" : "+this.psHold[c].stored);
			break;
		case "CABAL_COMMON_SPECIALMARKETX":
			this.cycleScreens();
			break;
		case "CABAL_COMMON_SPECIALMARKETY":
			if(!this.psHold.length){
				mission.runScreen({title:"Loaded Cargo",background:"cabal_common_specialmarkets.png",overlay:"cabal_common_specialmarkets_ov.png",choicesKey:"CABAL_COMMON_SPECIALMARKET_OVERVIEW_2",music:"cabal_common_specialmarkets.ogg"},this.choiceEval);
				mission.addMessageText("No special good in your hold.");
			} else {
				var a = this.holdPage*10,chc;
				if(this.psHold.length-1<10){
					a = 0;
					chc = "CABAL_COMMON_SPECIALMARKET_OVERVIEW_2";
				} else chc = "CABAL_COMMON_SPECIALMARKET_OVERVIEW_3";
				var b = this.helper.clamp(this.psHold.length,99,0);
				b = Math.min(b,a+10);
				mission.runScreen({title:"Loaded Cargo",background:"cabal_common_specialmarkets.png",overlay:"cabal_common_specialmarkets_ov.png",choicesKey:chc,music:"cabal_common_specialmarkets.ogg"},this.choiceEval);
				for(var c=a;c<b;c++) mission.addMessageText(this.helper.strToWidth(this.psHold[c].item,14)+" : "+this.psHold[c].stored);
			}
			break;
		case "CABAL_COMMON_SPECIALMARKETZ":
			this.shipWillLaunchFromStation();
			break;
		default: log("Choice not handled.");
	}
	return;
};
this.Cabal_Common_Keyboard_Output = function(input){worldScripts.Cabal_Common_SpecialMarkets.keyboardEval(input); return;};
this.keyboardEval = function(input)
{
	if(!input || input==="" || input==="0"){
		this.cycleScreens();
		return;
	}
	var cr = parseInt(input,null);
	if(this.buy){
		if(!player.ship.cargoSpaceAvailable || player.ship.cargoSpaceAvailable<this.store.totUnits){
			this.snd.sound = "ccl_markets_fail.ogg";
		} else if(player.credits>=cr*this.store.totBuy){
			if(!this.store.totUnits){
				this.snd.sound = "ccl_markets_nounits.ogg";
			} else {
				if(this.store.totUnits<cr) cr = this.store.totUnits;
				if(!this.curItem.contract){
					if(player.ship.cargoSpaceAvailable<player.ship.cargoSpaceCapacity){
						this.snd.sound = "ccl_markets_fail.ogg";
						this.snd.play();
						this.cycleScreens();
						return;
					}
					if(this.curIndex===-1) this.psHold.push({item:this.curItem.item,stored:cr});
					else this.psHold[this.curIndex].stored += cr;
				}
				player.credits -= cr*this.store.totBuy;
				this.trades[this.scr].blend = clock.days+Math.ceil(Math.log(this.curItem.priceclass)); // Only one trade? - TODO
				if(!this.curItem.legal){
					this.inspection++;
					if(this.rockOn.indexOf(this.curItem.item)===-1) this.rockOn.push(this.curItem.item);
				}
				if(this.curItem.block) player.ship.useSpecialCargo(this.curItem.item);
				else if(this.curItem.contract){
					var t = System.infoForSystem(galaxyNumber,system.ID).distanceToSystem(System.infoForSystem(galaxyNumber,this.store.tarSys))*1.3;
					player.ship.awardContract(cr,this.curItem.contractDesc,system.ID,this.store.tarSys,clock.seconds+t*24*3600,cr*this.store.totSell)
				}
				this.snd.sound = "ccl_markets_bought.ogg";
			}
		} else this.snd.sound = "ccl_markets_creditsfail.ogg";
		this.snd.play();
		this.cycleScreens();
	} else {
		if(this.psHold[this.curIndex].stored){
			if(this.psHold[this.curIndex].stored<cr) cr = this.psHold[this.curIndex].stored;
			if(worldScripts[this.trades[this.scr].cb] && worldScripts[this.trades[this.scr].cb][this.trades[this.scr].cbb]){
				if(this.curItem.buyID===system.ID) this.trades[this.scr].avail += cr;
				player.credits += cr*this.store.totSell;
				this.psHold[this.curIndex].stored -= cr;
				if(this.curItem.block && player.ship.specialCargo===this.curItem.item) player.ship.removeAllCargo();
				if(!this.psHold[this.curIndex].stored){
					this.inspection--;
					if(this.inspection<0) this.inspection=0;
					this.rockOn = this.helper.arrRemoveByValue(this.rockOn,this.psHold[this.curIndex].item);
				}
				var check = worldScripts[this.trades[this.scr].cb][this.trades[this.scr].cbb](this.psHold[this.curIndex].item,cr);
				if(!this.psHold[this.curIndex].stored && this.curItem.oneShot) this.removeTradeItem(this.curItem.item);
				if(check) this.snd.sound = "ccl_markets_complete.ogg";
				else {
					this.snd.sound = "ccl_markets_sold.ogg";
					this.snd.play();
					this.mso = true;
					return; // OXPs displays own screen
				}
			} else this.snd.sound = "ccl_markets_fail.ogg";
		} else this.snd.sound = "ccl_markets_nounits.ogg";
		this.snd.play();
		this.cycleScreens();
	}
	return;
};
this.missionScreenOpportunity = function()
{
	if(this.mso) this.cycleScreens();
};
this.shipEnteredStationAegis = function(station)
{
	if(station && this.inspection && player.ship.isValid){
		if(this.rockOn.length && Math.random()>0.9){
			if(worldScripts.vector_insp) worldScripts.vector_insp.startInspection(station,this.rockOn);
			else {
				var pen = Math.ceil(Math.sqrt(system.government+1)*this.rockOn.length);
				player.addMessageToArrivalReport("You're fined with "+pen+" Credits for trading in illegal goods without license.");
				player.credits -= pen;
			}
		}
	}
};
/*
this.specialCargo = function(what,amount)
{
	return(true);
};
*/
Scripts/cabal_common_comm_eq.js
"use strict";
this.name = "cabal_common_comm_eq";
this.author = "Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Secure comms channels - EQ script.";
this.version = "1.7";

this.activated = function()
{
	if(!this.beep){
		this.beep = new SoundSource();
		this.beep.sound = "[cabal_common_commbeep]";
	}
	if(worldScripts.Cabal_Common_Comms.commChannelChanged){
		this.store = worldScripts.Cabal_Common_Comms.commChannels;
		worldScripts.Cabal_Common_Comms.commChannelChanged = false;
		if(this.control) delete this.control;
	}
	if(!this.store) return;
	if(!this.control || ((new Date()).getTime()-this.control.lastPress)>5000){
		this.control = {actions:this.store,mode:0,cycleA:0,cycleB:0,selected:0,lastPress:1};
		var current = this.control.actions[this.control.cycleA];
		player.consoleMessage(current.display,2);
	} else {
		if(((new Date()).getTime()-this.control.lastPress)<1200){
			var current;
			if(!this.control.mode){
				this.control.cycleA++;
				if(this.control.cycleA>this.control.actions.length-1) this.control.cycleA = 0;
				this.control.selected = this.control.cycleA;
				current = this.control.actions[this.control.cycleA];
			} else {
				this.control.cycleB++;
				if(this.control.cycleB>this.control.actions[this.control.cycleA].react.length-1) this.control.cycleB = 0;
				current = this.control.actions[this.control.cycleA].react[this.control.cycleB];
			}
			player.consoleMessage(current.display,2);
		} else {
			var current,shorter;
			if(!this.control.mode) current = this.control.actions[this.control.cycleA];
			else current = this.control.actions[this.control.cycleA].react[this.control.cycleB];
			switch(this.control.mode){
				case 0:
					if(current.display==="Discard"){
						this.beep.play();
						delete this.control;
						player.consoleMessage('Cleared.',2);
						return;
					} else {
						if(current.display==="Open channel"){
							if(player.ship.target) this.requestChannel();
							else player.consoleMessage('No target.',2);
							delete this.control;
							this.beep.play();
							return;
						} else {
							this.control.cycleB = 0;
							this.control.mode = 1;
							current = this.control.actions[this.control.cycleA].react[this.control.cycleB];
						}
					}
					player.consoleMessage(current.display,2);
					break;
				case 1:
					if(current.display==="Back"){
						this.control.cycleA = this.control.selected;
						this.control.cycleB = 0;
						this.control.mode = 0;
						current = this.control.actions[this.control.cycleA];
					} else {
						shorter = this.control.actions[this.control.cycleA];
						if(shorter.ent){
							if(shorter.who.isValid){
								if(shorter.noDist || shorter.who.position.distanceTo(player.ship.position)<25600){
									shorter.who.script[shorter.callback](this.control.cycleB);
								} else player.consoleMessage('Signal weak',2);
							}
						} else {
							if(worldScripts[shorter.who] && !worldScripts[shorter.who].deactivated){
								if(shorter.eID && shorter.eID.isValid){
									if(shorter.eID.position.distanceTo(player.ship.position)<25600){
										worldScripts[shorter.who][shorter.callback](this.control.cycleB,shorter.pID,shorter.react[this.control.cycleB]);
									} else player.consoleMessage('Signal weak',2);
								} else worldScripts[shorter.who][shorter.callback](this.control.cycleB,shorter.pID);
							}
						}
						delete this.control;
						this.beep.play();
						return;
					}
					player.consoleMessage(current.display,2);
					break;
			}
		}
	}
	this.control.lastPress = (new Date()).getTime();
	this.beep.play();
};
this.requestChannel = function()
{
	var pst = player.ship.target;
	if(!pst.isShip || pst.isCargo || pst.isWeapon || pst.isRock || pst.isDerelict || !pst.isPiloted){
		player.consoleMessage('Not possible.',2);
		return;
	}
	this.patchedIDs = worldScripts.Cabal_Common_Comms.patchedIDs;
	if(this.patchedIDs.indexOf(pst.entityPersonality)===-1){
		if(pst.scriptInfo && pst.scriptInfo.ccl_secureChannel){
			var channel = pst.scriptInfo.ccl_secureChannel;
			if(pst.script[channel]){
				if(worldScripts.Cabal_Common_Comms.addToComm(pst.script[channel])) player.consoleMessage('Channel created.',2);
				return;
			} else {
				if(pst.scriptInfo.ccl_secureChannelPrep){
					if(pst.script[pst.scriptInfo.ccl_secureChannelPrep]){
						pst.script[pst.scriptInfo.ccl_secureChannelPrep]();
						var channel = pst.scriptInfo.ccl_secureChannel;
						if(worldScripts.Cabal_Common_Comms.addToComm(pst.script[channel])) player.consoleMessage('Channel created.',2);
						return;
					} else player.consoleMessage('Not available.',2);
				} else player.consoleMessage('Not available.',2);
			}
		} else player.consoleMessage('Request denied.',2);
	} else player.consoleMessage('Already done.',2);
	return;
};
Scripts/cabal_common_oxps_mine.js
"use strict";
this.name = "cabal_common_oxps_mine";
this.author = "Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Boooh.";
this.version = "1.7";

this.shipDied = function()
{
	if(this.ship.name==="CleanerMine") this.ship.spawn("cabal_common_oxps_minesub",5);
	else return;
	if(player.ship.isValid && this.ship.position.distanceTo(player.ship.position)<25600){
		var s = new SoundSource();
		s.sound = "cabal_common_bang.ogg";
		s.play();
	}
	return;
};
Scripts/cabal_common_oxps_ov.js
"use strict";
this.name = "cabal_common_oxps_ov";
this.author = "Svengali";
this.copyright = "(C)2010-2013, License:CC-by";
this.description = "Generic Overlay script.";
this.version = "1.7";

/*	As view_positions are not exposed positioning is practically impossible
	if shaders are off and therefor only roughly implemented.
*/
// Defaults
this.cclov_tar = player.ship;
this.cclov_blendC = -1;
this.cclov_blendI = -1;
this.cclov_blendA = -1;
this.cclov_noShade = false;

this.effectSpawned = function()
{
	this.ovFCBID = addFrameCallback(this.repos.bind(this));
}
this.effectRemoved = function()
{
	removeFrameCallback(this.ovFCBID);
}
this.repos = function(delta)
{
	if(!player.ship.isValid || !this.cclov_tar.isValid) this.visualEffect.remove();
	if(!delta) return;
	if(this.cclov_blendA!==-1 && clock.absoluteSeconds>this.cclov_blendA) this.visualEffect.remove();
	else {
		if(this.visualEffect.shaderFloat1<1) this.visualEffect.shaderFloat1 += delta;
		if(this.visualEffect.shaderFloat2>0){
			this.visualEffect.shaderFloat2 -= delta;
			if(this.visualEffect.shaderInt1>0) this.visualEffect.shaderInt1 -= 1;
			if(this.visualEffect.shaderInt2>0) this.visualEffect.shaderInt2 -= 1;
		} else {
			if(this.cclov_blendC!==-1 && clock.absoluteSeconds>this.cclov_blendC){
				if(this.visualEffect.shaderInt1<20) this.visualEffect.shaderInt1 += 1;
				else if(this.cclov_autoremove){
					this.visualEffect.remove();
					return;
				}
			}
			if(this.cclov_blendI!==-1 && clock.absoluteSeconds>this.cclov_blendI){
				if(this.visualEffect.shaderInt2<20) this.visualEffect.shaderInt2 += 1;
				else if(this.cclov_autoremove){
					this.visualEffect.remove();
					return;
				}
			}
		}
		if(!this.cclov_tar.isValid) return; // needs this check. strange.
		var bb = this.cclov_tar.boundingBox;
		if(this.cclov_tar.isPlayer){
			switch(this.cclov_tar.viewDirection){
				case "VIEW_FORWARD":
					if(!this.cclov_noShade) this.visualEffect.position = this.cclov_tar.position.add(this.cclov_tar.vectorForward.multiply(bb.z));
					else this.visualEffect.position = this.cclov_tar.position.add(this.cclov_tar.vectorForward.multiply(75)).add(this.cclov_tar.vectorUp.multiply(15)).subtract(this.visualEffect.shaderVector1.multiply(1.2).rotateBy(this.cclov_tar.orientation));
					break;
				case "VIEW_AFT":
					if(!this.cclov_noShade) this.visualEffect.position = this.cclov_tar.position.subtract(this.cclov_tar.vectorForward.multiply(bb.z));
					else this.visualEffect.position = this.cclov_tar.position.subtract(this.cclov_tar.vectorForward.multiply(75)).add(this.cclov_tar.vectorUp.multiply(15)).subtract(this.visualEffect.shaderVector1.multiply(1.2).rotateBy(this.cclov_tar.orientation));
					break;
				case "VIEW_PORT":
					if(!this.cclov_noShade) this.visualEffect.position = this.cclov_tar.position.subtract(this.cclov_tar.vectorRight.multiply(bb.x));
					else this.visualEffect.position = this.cclov_tar.position.subtract(this.cclov_tar.vectorRight.multiply(85)).add(this.cclov_tar.vectorUp.multiply(10)).subtract(this.visualEffect.shaderVector1.multiply(1.2).rotateBy(this.cclov_tar.orientation));
					break;
				case "VIEW_STARBOARD":
					if(!this.cclov_noShade) this.visualEffect.position = this.cclov_tar.position.add(this.cclov_tar.vectorRight.multiply(bb.x));
					else this.visualEffect.position = this.cclov_tar.position.add(this.cclov_tar.vectorRight.multiply(85)).add(this.cclov_tar.vectorUp.multiply(10)).subtract(this.visualEffect.shaderVector1.multiply(1.2).rotateBy(this.cclov_tar.orientation));
					break;
				case "VIEW_CUSTOM":
					this.visualEffect.position = this.cclov_tar.position;
					break;
				case "VIEW_GUI_DISPLAY":
					this.visualEffect.shaderFloat1 = 0;
					break;
			}
		}
		if(!this.cclov_tar.isValid) return; // needs this check. strange.
		if(this.cclov_noShade) this.visualEffect.orientation = this.cclov_tar.orientation;
	}
	return;
};