Back to Index Page generated: Jun 13, 2026, 7:54:51 PM

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
Dependent Expansions
  • oolite.oxp.Lone_Wolf.ShieldCycler:1.12
  • oolite.oxp.Norby.Ambience_Collection:1.3
  • oolite.oxp.Svengali.Hyperradio:1.26.1
  • oolite.oxp.Svengali.OXPConfig:2.3.4
  • 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

    Relationships Diagram

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