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

Expansion Library

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Library is a collection of useful snippets, helpers and resources to simplify some common tasks used by AddOns. Library is a collection of useful snippets, helpers and resources to simplify some common tasks used by AddOns.
Identifier oolite.oxp.Svengali.Library oolite.oxp.Svengali.Library
Title Library Library
Category Misc Misc
Author Svengali & BlackWolf Svengali & BlackWolf
Version 1.7.1 1.7.1
Tags config, configuration, effects, helper, library, misc, parent, tools, sanity, shaders config, configuration, effects, helper, library, misc, parent, tools, sanity, shaders
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL http://wiki.alioth.net/index.php/Library n/a
Download URL https://wiki.alioth.net/img_auth.php/f/f2/Library1.7.1.oxz n/a
License CC-by-nc-sa 4.0, partly MIT CC-by-nc-sa 4.0, partly MIT
File Size n/a
Upload date 1610873512

Documentation

Also read http://wiki.alioth.net/index.php/Library

Readme.txt

Library 1.7.1 for Oolite, Revision: 68
Copyright 2016-2018 by Svengali & BlackWolf.
Licences: see below
November 2018

REQUIREMENTS:
- Oolite v1.88

DOCUMENTATION:
- http://wiki.alioth.net/index.php/Library


PROBLEMS:
In case of problems, please report it: http://aegidian.org/bb/viewtopic.php?f=4&t=18074

Please 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)

Licences:
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 Unported License.
To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ or
 send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA.

The avatar pics and most of the character pics have been created by BlackWolf!

Many thanks to:
Ahruman, Cody, Commander McLane, Dr.J R Stockton, Eric Walch, Getafix, Michael Werle, Lonewolf, Nicholas Zakas, Nikos Barkas, Norbert Nagy, Paul Bourke, phkb, Smivs and Terry Yuen.

Equipment

This expansion declares no equipment.

Ships

Name
lib_ms
lib_ms_cube_a
lib_ms_cubes
lib_ms_exhaust
lib_ms_helper
lib_ms_helper12
lib_ms_helper12x
lib_ms_helper3x
lib_ms_helper3y
lib_ms_helper4y
lib_ms_helper6x
lib_ms_helper6y
lib_ms_helper_cut
lib_ms_laser
lib_ms_laserDist
lib_ms_moon
lib_ms_ov
Boa

Models

This expansion declares no models.

Scripts

Path
Scripts/Lib_2DCollision.js
/* jshint bitwise:false, forin:false */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_2DCollision";

/** _inBox() - Checks if point is inside the bounding box
	@box - Array. 4 positions in a.x,a.y,b.x,b.y
	@pos - Vector. Position to be checked
	@return - Boolean. True if inside
*/
this._inBox = function(box,pos){
	if(pos.x<box[0] || pos.x>box[1] || pos.y<box[2] || pos.y>box[3]) return false;
	return true;
};

/** _inPoly() - Checks if a point is inside a polygon
	@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 - Boolean. True if inside
*/
this._inPoly = 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;
};

/** _onLine() - Checks if point is on a specified line
	@pa - Vector. Starting point of the line
	@pb - Vector. End point of the line
	@pc - Vector. Position to be checked
	@tol - Number. Tolerance. Default 0.01
	@return - Boolean. True if on line (within tolerance)
*/
this._onLine = function(pa,pb,pc,tol){
	var s,rnum,denom,check,distLine,distSegment,dist1,dist2;
	if(typeof(tol)!=='number') tol = 0.01;
	rnum = (pc.x-pa.x)*(pb.x-pa.x)+(pc.y-pa.y)*(pb.y-pa.y);
	denom = (pb.x-pa.x)*(pb.x-pa.x)+(pb.y-pa.y)*(pb.y-pa.y);
	check = rnum/denom;
	s = ((pa.y-pc.y)*(pb.x-pa.x)-(pa.x-pc.x)*(pb.y-pa.y))/denom;
	distLine = Math.abs(s)*Math.sqrt(denom);
	if(check>=0 && check<=1) distSegment = distLine;
	else {
		dist1 = (pc.x-pa.x)*(pc.x-pa.x)+(pc.y-pa.y)*(pc.y-pa.y);
		dist2 = (pc.x-pb.x)*(pc.x-pb.x)+(pc.y-pb.y)*(pc.y-pb.y);
		if(dist1<dist2) distSegment = Math.sqrt(dist1);
		else distSegment = Math.sqrt(dist2);
	}
	return (distSegment<tol);
};
}).call(this);
Scripts/Lib_Animator.js
/* jshint bitwise:false, forin:false */
/* global _Animator,Sound,SoundSource,Timer,Vector3D,addFrameCallback,clock,isValidFrameCallback,mission,player,removeFrameCallback,setScreenBackground,setScreenOverlay,worldScripts */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_Animator";
/** 
* TODO:
* 	Flags - append
*	Fixme - modifyMaterials
*/
this._start = function(ani){
	if(!player.ship.docked || !ani || !ani.model || !ani.flow || (this._a && !ani.replace && !ani.capture)) return false;
	if((this._a && (!ani.replace || !ani.capture) ) && (!mission.displayModel || mission.displayModel.dataKey!==ani.model)) return false;
	var obj = worldScripts.Lib_Main._lib.objClone(ani),hud,hudHide,i,msc;
	if(this._a && (obj.replace || obj.capture)){
		this._a.reset();
		if(this.$anim){
			hud = this.$anim.prevHUD;
			hudHide = this.$anim.prevHUDHide;
		}
		this._cleanUp(1);
	}
	this._a = new this._Animator();
	if(!obj.capture){
		msc = {
			background: null,
			choices: (obj.choices?obj.choices:null),
			choicesKey: (obj.choicesKey?obj.choicesKey:null),
			model: (obj.model?obj.model:null),
			music: (obj.music?obj.music:null),
			overlay: null,
			screenID: (obj.screenID?obj.screenID:"Lib_Animator"),
			spinModel: false,
			title: (obj.title?obj.title:"")
		};
		if(obj.text) msc.message = obj.text;
		if(obj.background){
			msc.background = obj.background;
			this._a.bg.name = obj.background;
		}
		if(obj.overlay){
			msc.overlay = obj.overlay;
			this._a.ov.name = obj.overlay;
		}
		if(obj.fadeIn) msc.overlay = {name:"lib_blend10.png",width:1024,height:512};
	}
	this.$anim = {
		count: 0,
		index: 0,
		flow: obj.flow,
		caller: (obj.caller?obj.caller:null),
		callback: (obj.callback?obj.callback:null),
		checkpoint: (obj.checkpoint?obj.checkpoint:null),
		prevHUD: (hud?hud:player.ship.hud),
		prevHUDHide: (typeof hudHide!=="undefined"?hudHide:player.ship.hudHidden),
		custom: obj.custom,
		capture: obj.capture?obj.capture:null,
		md: obj.model
	};
	if(!obj.capture){
		if(obj.hud){
			player.ship.hud = obj.hud;
			player.ship.hudHidden = false;
		} else player.ship.hudHidden = true;
		mission.runScreen(msc,this._choiceEval);
	}
	if(!mission.displayModel) return false;
	this._a.init(mission.displayModel);
	this.$waitUntil = -1;
	if(obj.corner) this._a.screenCornerPos(obj.corner);
	if(obj.aPos) for(i=0;i<obj.aPos.length;i++) this._a.setPosition(obj.aPos[i]);
	if(obj.aExh) for(i=0;i<obj.aExh.length;i++) this._a.setExhaust(obj.aExh[i]);
	if(obj.aOris) for(i=0;i<obj.aOris.length;i++) this._a.setOrientation(obj.aOris[i]);
	if(obj.aBinds) for(i=0;i<obj.aBinds.length;i++) this._a.setBinding(obj.aBinds[i]);
	if(obj.aProps) for(i=0;i<obj.aProps.length;i++) this._a.setProp(obj.aProps[i]);
	if(obj.bPos) for(i=0;i<obj.bPos.length;i++) this._a.setPosition(obj.bPos[i]);
	if(obj.bOris) for(i=0;i<obj.bOris.length;i++) this._a.setOrientation(obj.bOris[i]);
	if(obj.bBinds) for(i=0;i<obj.bBinds.length;i++) this._a.setBinding(obj.bBinds[i]);
	if(obj.bProps) for(i=0;i<obj.bProps.length;i++) this._a.setProp(obj.bProps[i]);
	if(obj.shadeMaps) for(i=0;i<obj.shadeMaps.length;i++) this._a.setShaderProp(obj.shadeMaps[i]);
	if(obj.pilots) for(i=0;i<obj.pilots.length;i++) this._a._setPilot(obj.pilots[i]);
	if(obj.subRots) for(i=0;i<obj.subRots.length;i++) this._a.subs[obj.subRots[i].e].subEntityRotation = obj.subRots[i].r;
	if(obj.subTex) for(i=0;i<obj.subTex.length;i++) this._a.setMaterials(obj.subTex[i]);
	this.$sndA = new SoundSource();
	this.$sndB = new SoundSource();
	if(obj.delta) this._a.defDelta = obj.delta;
	else this._a.defDelta = 0.005;
	if(!this.$animTimer) this.$animTimer = new Timer(this,this._doAnimTimer,0,0.25);
	else this.$animTimer.start();
	return true;
};
this._cleanUp = function(all){
	if(this.$animTimer) this.$animTimer.stop();
	delete this.$animTimer;
	if(all){
		delete this.$anim;
		delete this.$sndA;
		delete this.$sndB;
		delete this._a;
	}
};
this._choiceEval = function(choice){worldScripts.Lib_Animator._choice(choice); return;};
this._choice = function(choice){
	this._a.reset();
	player.ship.hud = this.$anim.prevHUD;
	player.ship.hudHidden = this.$anim.prevHUDHide;
	var a = this.$anim.caller, b = this.$anim.callback;
	this._cleanUp(1);
	if(a && b) worldScripts[a][b](choice);
	return;
};
this._doAnimTimer = function(){
	if(!this.$anim.flow.length || !mission.displayModel || !mission.displayModel.isValid || this.$anim.index>=this.$anim.flow.length){
		this._a.reset();
		this._cleanUp();
		return;
	}
	var cur = this.$anim.flow[this.$anim.index],i,p;
	if(this.$anim.count >= cur[0]){
		// [time,cmd,obj,other]
		switch(cur[1]){
			case "reset": this._a.reset(); break;
			case "clr": this._a.clear(); break;
			case "clrMov": this._a.clearMoves(); break;
			case "kill": // Get CPU usage down
				if(this.$anim.index===this.$anim.flow.length-1){
					mission.displayModel.remove();
					if(cur[2] && cur[2].txt) mission.addMessageText(cur[2].txt);
				}
				break;
			case "rotw": this._a.rotateW(cur[2]); break;
			case "rot": this._a.rotate(cur[2]); break;
			case "rotTo": this._a.rotateTo(cur[2]); break;
			case "rotX": this._a.rotateXDeg(cur[2]); break;
			case "rotY": this._a.rotateYDeg(cur[2]); break;
			case "rotZ": this._a.rotateZDeg(cur[2]); break;
			case "stopRot": this._a.stopRotate(cur[2]); break;
			case "fly": this._a.flight(cur[2]); break;
			case "flyTo": this._a.flightTo(cur[2]); break;
			case "stopFly": this._a.stopFlight(cur[2]); break;
			case "velo": this._a.velocity(cur[2]); break;
			case "veloTo": this._a.velocityTo(cur[2]); break;
			case "veloSet": this._a.setVelocity(cur[2]); break;
			case "stopVelo": this._a.stopVelocity(cur[2]); break;
			case "walk": this._a.walk(cur[2]); break;
			case "walkTo": this._a.walkTo(cur[2]); break;
			case "stopWalk": this._a.stopWalk(cur[2]); break;
			case "aiDest": this._a.aiDestination(cur[2]); break;
			case "aiFace": this._a.aiFace(cur[2]); break;
			case "aiFly": this._a.aiFly(cur[2]); break;
			case "aiHold": this._a.aiHold(cur[2]); break;
			case "aiRange": this._a.aiRange(cur[2]); break;
			case "aiSpeed": this._a.aiSpeed(cur[2]); break;
			case "aiStop": this._a.aiStop(cur[2]); break;
			case "zoom": this._a.zoom(cur[2]); break;
			case "zoomTo": this._a.zoomTo(cur[2]); break;
			case "stopZoom": this._a.stopZoom(cur[2]); break;
			case "prop": this._a.setProp(cur[2]); break;
			case "matSet": this._a.setMaterials(cur[2]); break;
			case "matMod": this._a.modifyMaterials(cur[2]); break;
			case "lit": this._a.highlight(cur[2]); break;
			case "tex": this._a.setTextures(cur[2]); break;
			case "shadeSet": this._a.setShader(cur[2]); break;
			case "shadeMod": this._a.setShaderProp(cur[2]); break;
			case "sun": this._a.sun(cur[2]); break;
			case "twinkle": this._a.twinkle(cur[2]); break;
			case "moon": this._a.moon(cur[2]); break;
			case "boom": this._a.explosion(cur[2]); break;
			case "hyper":
				this._a.hyper(cur[2]);
				this.$sndB.playSound("[bgs_fxWitch]");
				break;
			case "shoot":
				var d = [0.00001,clock.absoluteSeconds,0];
				//if(cur[2].rear) d[2] = cur[2].rear;
				if(this._a.cur[cur[2].tg].ang>0.098){
					this.$sndA.playSound("[player-laser-miss]");
				} else {
					d[0] = 1/this._a.cur[cur[2].tg].mag;
					this.$sndA.playSound("[player-laser-hit]");
				}
				this._a.setProp({e:cur[2].e,p:"destination",v:d});
				break;
			case "laser":
				var act, tg, mag, ang, v;
				var d = [0.00001,clock.absoluteSeconds,0];
				if(cur[2].e>-1) act = this._a.subs[cur[2].e];
				else act = this._a.ent;
				if(cur[2].tg>-1) tg = this._a.subs[cur[2].tg];
				else tg = this._a.ent;
				v = act.position.subtract(tg.position).direction().multiply(-1);
				mag = act.position.subtract(tg.position).magnitude();
				ang = act.heading.angleTo(v);
				if(ang>0.098){
					this.$sndA.playSound("[player-laser-miss]");
				} else {
					d[0] = 1/mag;
					this.$sndA.playSound("[player-laser-hit]");
				}
				this._a.setProp({e:cur[2].e,p:"destination",v:d});
				break;
			case "bind": this._a.setBinding(cur[2]); break;
			case "stopBind": this._a.stopBinding(cur[2]); break;
			case "pos": this._a.setPosition(cur[2]); break;
			case "posTo": this._a.setPositionTo(cur[2]); break;
			case "posFl": this._a.setFlasherPositionTo(cur[2]); break;
			case "ori": this._a.setOrientation(cur[2]); break;
			case "snap": this._a.snapIn(cur[2]); break;
			case "bg": this._a.setBackground(cur[2]); break;
			case "bgZoom": this._a.zoomBackground(cur[2]); break;
			case "bgZoomTo": this._a.zoomBackgroundTo(cur[2]); break;
			case "bgStop": this._a.stopBackground(); break;
			case "bgClr": this._a.clearBackground(); break;
			case "bgRes": this._a.resetBackground(cur[2]); break;
			case "ov": this._a.setOverlay(cur[2]); break;
			case "ovZoom": this._a.zoomOverlay(cur[2]); break;
			case "ovStop": this._a.stopOverlay(); break;
			case "ovClr": this._a.clearOverlay(); break;
			case "ovFadeIn": this._a.fadeIn(cur[2]); break;
			case "ovFadeOut": this._a.fadeOut(cur[2]); break;
			case "corner": this._a.screenCornerPos(cur[2]); break;
			case "txt": mission.addMessageText(cur[2]); break;
			case "snd1": this.$sndA.playSound(cur[2].snd); break;
			case "snd2": this.$sndB.playSound(cur[2].snd); break;
			case "mus": Sound.playMusic(cur[2].snd); break;
			case "custom": this.$anim.custom[cur[2]](); break;
			case "goto": this._goto(cur[2]); break;
			case "check":
				var ch = false;
				if(this.$anim.caller && this.$anim.checkpoint) ch = worldScripts[this.$anim.caller][this.$anim.checkpoint](cur[0]);
				if(typeof ch==='number') this._goto(ch);
				else {
					if(!ch){this._a.reset(); this._cleanUp();}
				}
				break;
			case "waitExt":
				if(this.$waitUntil>-1){
					this._goto(this.$waitUntil);
					this.$waitUntil = -1;
				} else {
					this.$anim.index--;
					this.$anim.count--;
				}
				break;
			case "speak":
				this._a._pilotSpeak(cur[2]);
				if(cur[2].snd) this.$sndB.playSound(cur[2].snd);
				break;
		}
		if(cur[3]){
			for(i=0;i<cur[3].length;i++){
				p = cur[3][i];
				switch(p.id){
					case "txt": mission.addMessageText(p.txt); break;
					case "snd1": this.$sndA.playSound(p.snd); break;
					case "snd2": this.$sndB.playSound(p.snd); break;
					case "mus": Sound.playMusic(p.snd); break;
				}
			}
		}
		this.$anim.index++;
	}
	this.$anim.count++;
	return;
};
this._goto = function(cmp){
	var from,to;
	if(cmp>this.$anim.count){
		from = this.$anim.index;
		to = this.$anim.flow.length;
	} else {
		from = 0;
		to = this.$anim.index;
	}
	for(var i=from;i<to;i++){
		if(this.$anim.flow[i][0]===cmp){
			this.$anim.index = i-1;
			this.$anim.count = cmp-1;
			break;
		}
	}
};
/** Animation API
*/
this._Animator = function(){};
_Animator.prototype = {
	constructor: _Animator,
	w: 1024,
	h: 512,
	fcb: 0,
	fcbs: [],
	bg: {name:null,width:this.w,height:this.h,mw:1,mh:1},
	ov: {name:null,width:this.w,height:this.h,mw:1,mh:1},
	ovFade: {n:10,dir:1,t:2,cur:0},
	defDelta: 0.005,
	ent: null,
	subs: null,
	flow: {
		bind:{},
		flight:{},
		props:{},
		rotate:{},
		velocity:{},
		walk:{},
		zoom:{},
		bg:{},
		ov:{},
		aiDest:{}
	},
	cur: {},
	/** ent = Entity. Must be called before anything else!
	*/
	init: function(ent){
		this.cur = {"-1":{mf:0,ang:0,mag:0,tOri:null}};
		this.ent = ent;
		if(ent.subEntities && ent.subEntities.length){
			this.subs = ent.subEntities;
			for(var i=0;i<ent.subEntities.length;i++) this.cur[i] = {mf:0,ang:0,mag:0,tOri:null};
		}
		else this.subs = null;
		this.w = 1024;
		this.h = 512;
		this.bg.width = this.w;
		this.bg.height = this.h;
		this.ov.width = this.w;
		this.ov.height = this.h;
	},
	reset: function(){
		this.clear();
		this.bg = {name:null,width:this.w,height:this.h,mw:1,mh:1};
		this.ov = {name:null,width:this.w,height:this.h,mw:1,mh:1};
		this.ovFade = {n:10,dir:1,t:2,cur:0};
		this.ent = null;
		this.subs = null;
		this.defDelta = 0.005;
	},
	clear: function(){
		this._removeFCBs();
		this.flow = {
			bind:{},
			flight:{},
			props:{},
			rotate:{},
			velocity:{},
			walk:{},
			zoom:{},
			bg:{},
			ov:{},
			aiDest:{}
		};
	},
	clearMoves: function(){
		var m = ["flight","rotate","velocity","walk","zoom","aiDest"],k;
		for(var o=0;o<m.length;o++){
			k = Object.keys(this.flow[m[o]]);
			for(var i=0;i<k.length;i++){
				this._stopIt({e:k[i]},m[o]);
			}
		}
	},
	/** e, t, rx,ry,rz [, z, da, db]		World!
	*/
	rotateW: function(obj){
		var c = [this.ent,this.subs,0,this.defDelta,obj];
		this.fcb = addFrameCallback(function(delta){
			if(!delta || !c[0] || !c[0].isValid) return;
			var f=1, bz=1, da=1, db=1, act;
			if(c[3]>delta) f = 1/(c[3]/delta);
			else f = 1-(c[3]-delta);
			c[2] += delta;
			if(c[2]>=c[4].t) return;
			if(c[4].e>-1) act = c[1][c[4].e];
			else act = c[0];
			if(c[4].z) bz = Math.sqrt(Math.abs(act.position.z));
			if(c[4].da && c[2]<c[4].da) da = 1-((c[4].da-c[2])/c[4].da);
			if(c[4].db && c[2]>c[4].t-c[4].db) db = (c[4].t-c[2])/c[4].db;
			if(c[4].rx) act.orientation = act.orientation.rotateX(c[4].rx*da*db*bz*f);
			if(c[4].ry) act.orientation = act.orientation.rotateY(c[4].ry*da*db*bz*f);
			if(c[4].rz) act.orientation = act.orientation.rotateZ(c[4].rz*da*db*bz*f);
			return;
		});
		this._addTo("rotate",this.fcb,obj);
		return true;
	},
	/** e, t, rx,ry,rz [, z, da, db]		Model!
	*/
	rotate: function(obj){
		var c = [this.ent,this.subs,0,this.defDelta,obj];
		this.fcb = addFrameCallback(function(delta){
			if(!delta || !c[0] || !c[0].isValid) return;
			var f=1, bz=1, da=1, db=1, act;
			if(c[3]>delta) f = 1/(c[3]/delta);
			else f = 1-(c[3]-delta);
			c[2] += delta;
			if(c[2]>=c[4].t) return;
			if(c[4].e>-1) act = c[1][c[4].e];
			else act = c[0];
			if(c[4].z) bz = Math.sqrt(Math.abs(act.position.z));
			if(c[4].da && c[2]<c[4].da) da = 1-((c[4].da-c[2])/c[4].da);
			if(c[4].db && c[2]>c[4].t-c[4].db) db = (c[4].t-c[2])/c[4].db;
			if(c[4].rx) act.orientation = act.orientation.rotate(act.vectorRight,c[4].rx*da*db*bz*f);
			if(c[4].ry) act.orientation = act.orientation.rotate(act.vectorUp,c[4].ry*da*db*bz*f);
			if(c[4].rz) act.orientation = act.orientation.rotate(act.vectorForward,c[4].rz*da*db*bz*f);
			return;
		});
		this._addTo("rotate",this.fcb,obj);
		return true;
	},
	/** e, t, tg [, z, da, db, s]
	*/
	rotateTo: function(obj){
		this._stopIt(obj,"rotate");
		if(!obj.tg.isShip){
			if(typeof obj.tg==='number') obj.tg = this.subs[obj.tg];
			else if(obj.tg.constructor.name==="Array") obj.tgv = new Vector3D(obj.tg);
		}
		var c = [this.ent,this.subs,0,this.defDelta,obj,this.cur[obj.e]];
		this.fcb = addFrameCallback(function(delta){
			if(!delta || !c[0] || !c[0].isValid || !c[4].tg || (!c[4].tg.isValid && !c[4].tgv)) return;
			var f=1, bz=1, da=1, db=1, act, v, a, cr, steps = 0.01;
			if(c[3]>delta) f = 1/(c[3]/delta);
			else f = 1-(c[3]-delta);
			c[2] += delta;
			if(c[2]>=c[4].t) return;
			if(c[4].e>-1) act = c[1][c[4].e];
			else act = c[0];
			if(c[4].tgv){
				v = act.position.subtract(c[4].tgv).direction().multiply(-1);
				c[5].mag = act.position.subtract(c[4].tgv).magnitude();
			} else {
				v = act.position.subtract(c[4].tg.position).direction().multiply(-1);
				c[5].mag = act.position.subtract(c[4].tg.position).magnitude();
			}
			a = act.heading.angleTo(v);
			c[5].ang = a;
			if(c[4].z) bz = 1-1/Math.sqrt(Math.max(Math.abs(act.position.z),0.001));
			if(c[4].da && c[2]<c[4].da) da = 1-((c[4].da-c[2])/c[4].da);
			if(c[4].db && c[2]>c[4].t-c[4].db) db = (c[4].t-c[2])/c[4].db;
			if(c[4].s) steps = c[4].s*da;
			cr = act.heading.cross(v);
			act.orientation = act.orientation.rotate(cr,-a*steps*f*bz*da*db);
			return;
		});
		this._addTo("rotate",this.fcb,obj);
		return true;
	},
	/** e, t, deg [, z, da, db, inf]		World!
	*/
	rotateXDeg: function(obj){
		if(obj.e>-1) obj.nt = this.subs[obj.e].orientation.rotateX(0.017453293916206696*obj.deg);
		else obj.nt = this.ent.orientation.rotateX(0.017453293916206696*obj.deg);
		if(obj.inf) this.cur[obj.e].tOri = obj.nt;
		obj.st = obj.deg/(obj.t*1000);
		var c = [this.ent,this.subs,0,this.defDelta,obj];
		this.fcb = addFrameCallback(function(delta){
			if(!delta || !c[0] || !c[0].isValid) return;
			var f=1, bz=1, da=1, db=1, act, sp;
			if(c[3]>delta) f = 1/(c[3]/delta);
			else f = 1-(c[3]-delta);
			c[2] += delta;
			if(c[2]>=c[4].t) return;
			if(c[4].e>-1) act = c[1][c[4].e];
			else act = c[0];
			sp = act.orientation.dot(c[4].nt);
			if(sp>0.999) return;
			if(c[4].z) bz = Math.sqrt(Math.abs(act.position.z));
			if(c[4].da && c[2]<c[4].da) da = 1-((c[4].da-c[2])/c[4].da);
			if(c[4].db && c[2]>c[4].t-c[4].db) db = (c[4].t-c[2])/c[4].db;
			if(c[4].deg) act.orientation = act.orientation.rotateX(c[4].st*da*db*bz*f);
			return;
		});
		this._addTo("rotate",this.fcb,obj);
		return true;
	},
	/** e, t, deg [, z, da, db, inf]		World!
	*/
	rotateYDeg: function(obj){
		if(obj.e>-1) obj.nt = this.subs[obj.e].orientation.rotateY(0.017453293916206696*obj.deg);
		else obj.nt = this.ent.orientation.rotateY(0.017453293916206696*obj.deg);
		if(obj.inf) this.cur[obj.e].tOri = obj.nt;
		obj.st = obj.deg/(obj.t*1000);
//		obj.st = ((0.017453293916206696*obj.deg)/obj.t)*this.defDelta;
		var c = [this.ent,this.subs,0,this.defDelta,obj];
		this.fcb = addFrameCallback(function(delta){
			if(!delta || !c[0] || !c[0].isValid) return;
			var f=1, bz=1, da=1, db=1, act, sp;
			if(c[3]>delta) f = 1/(c[3]/delta);
			else f = 1-(c[3]-delta);
			c[2] += delta;
			if(c[2]>=c[4].t) return;
			if(c[4].e>-1) act = c[1][c[4].e];
			else act = c[0];
			sp = act.orientation.dot(c[4].nt);
			if(sp>0.9999) return;
			if(c[4].z) bz = Math.sqrt(Math.abs(act.position.z));
			if(c[4].da && c[2]<c[4].da) da = 1-((c[4].da-c[2])/c[4].da);
			if(c[4].db && c[2]>c[4].t-c[4].db) db = (c[4].t-c[2])/c[4].db;
			if(c[4].deg) act.orientation = act.orientation.rotateY(c[4].st*da*db*bz*f);
			return;
		});
		this._addTo("rotate",this.fcb,obj);
		return true;
	},
	/** e, t, deg [, z, da, db, inf]		World!
	*/
	rotateZDeg: function(obj){
		if(obj.e>-1) obj.nt = this.subs[obj.e].orientation.rotateZ(0.017453293916206696*obj.deg);
		else obj.nt = this.ent.orientation.rotateZ(0.017453293916206696*obj.deg);
		if(obj.inf) this.cur[obj.e].tOri = obj.nt;
		obj.st = obj.deg/(obj.t*1000);
		var c = [this.ent,this.subs,0,this.defDelta,obj];
		this.fcb = addFrameCallback(function(delta){
			if(!delta || !c[0] || !c[0].isValid) return;
			var f=1, bz=1, da=1, db=1, act, sp;
			if(c[3]>delta) f = 1/(c[3]/delta);
			else f = 1-(c[3]-delta);
			c[2] += delta;
			if(c[2]>=c[4].t) return;
			if(c[4].e>-1) act = c[1][c[4].e];
			else act = c[0];
			sp = act.orientation.dot(c[4].nt);
			if(sp>0.999) return;
			if(c[4].z) bz = Math.sqrt(Math.abs(act.position.z));
			if(c[4].da && c[2]<c[4].da) da = 1-((c[4].da-c[2])/c[4].da);
			if(c[4].db && c[2]>c[4].t-c[4].db) db = (c[4].t-c[2])/c[4].db;
			if(c[4].deg) act.orientation = act.orientation.rotateZ(c[4].st*da*db*bz*f);
			return;
		});
		this._addTo("rotate",this.fcb,obj);
		return true;
	},
	/** e
	*/
	stopRotate: function(obj){
		this._stopIt(obj,"rotate");
		return true;
	},
	/** e, t, mu,mr,mf, rx,ry,rz [,da, db, z, fu, en]
	*/
	flight: function(obj){
		this.rotate(obj);
		this.velocity(obj);
	},
	/** e, t, tg, s, mu,mr,mf [, da, db, z, fu, en]
	*/
	flightTo: function(obj){
		this.rotateTo(obj);
		this.velocityTo(obj);
	},
	/** e
	*/
	stopFlight: function(obj){
		this.stopVelocity(obj);
		this.stopRotate(obj);
		return true;
	},
	aiDestination: function(obj){
		var act;
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		this._stopIt(obj,"aiDest");
		if(obj.dest){
			if(typeof obj.dest==='number') act.destination = this.subs[obj.dest];
			else act.destination = obj.dest;
		} else {
			var c = [this.ent,this.subs,0,this.defDelta,obj,0];
			this.fcb = addFrameCallback(function(delta){
				if(!delta || !c[0] || !c[0].isValid) return;
				var act, st;
				c[2] += delta;
				c[5]++;
				// Updating only every 50. framecallback!!!
				if(c[2]>=c[4].t || c[5]<50) return;
				if(c[4].e>-1) act = c[1][c[4].e];
				else act = c[0];
				act.destination = c[1][c[4].tg].position;
				c[5] = 0;
				return;
			});
			this._addTo("aiDest",this.fcb,obj);
		}
	},
	aiFace: function(obj){
		this.aiDestination(obj);
		if(obj.e>-1) this.subs[obj.e].performFaceDestination();
		else this.ent.performFaceDestination();
	},
	aiFly: function(obj){
		this.aiSpeed(obj);
		this.aiRange(obj);
		this.aiDestination(obj);
		var act;
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		act.performFlyToRangeFromDestination();
	},
	aiHold: function(obj){
		if(obj.e>-1) this.subs[obj.e].performHold();
		else this.ent.performHold();
		this._stopIt(obj,"aiDest");
	},
	aiRange: function(obj){
		if(obj.e>-1) this.subs[obj.e].desiredRange = obj.rng;
		else this.ent.desiredRange = obj.rng;
	},
	aiSpeed: function(obj){
		if(obj.e>-1) this.subs[obj.e].desiredSpeed = obj.spd;
		else this.ent.desiredSpeed = obj.spd;
	},
	aiStop: function(obj){
		if(obj.e>-1) this.subs[obj.e].performStop();
		else this.ent.performStop();
		this._stopIt(obj,"aiDest");
	},
	/** e, t, mu,mr,mf [,da, db, z, fu, en]
	*/
	velocity: function(obj){
		var c = [this.ent,this.subs,0,this.defDelta,obj];
		this.fcb = addFrameCallback(function(delta){
			if(!delta || !c[0] || !c[0].isValid) return;
			var f=1, bz=1, da=1, db=1, act;
			if(c[3]>delta) f = 1/(c[3]/delta);
			else f = 1-(c[3]-delta);
			c[2] += delta;
			if(c[2]>=c[4].t) return;
			if(c[4].e>-1) act = c[1][c[4].e];
			else act = c[0];
			if(c[4].z) bz = Math.sqrt(Math.abs(act.position.z));
			if(c[4].da && c[2]<c[4].da) da = 1-((c[4].da-c[2])/c[4].da);
			if(c[4].db && c[2]>c[4].t-c[4].db) db = (c[4].t-c[2])/c[4].db;
			if(c[4].mu) act.velocity = act.vectorUp.multiply(c[4].mu*bz*da*db*f);
			if(c[4].mr) act.velocity = act.vectorRight.multiply(c[4].mr*bz*da*db*f);
			if(c[4].mf) act.velocity = act.vectorForward.multiply(c[4].mf*bz*da*db*f);
			if(c[4].fu) act.fuel = (act.velocity.magnitude()/act.maxSpeed)*7;
			if(c[4].en) act.energy = (act.velocity.magnitude()/act.maxSpeed)*act.maxEnergy;
			return;
		});
		this._addTo("velocity",this.fcb,obj);
		return true;
	},
	/** e, t, tg, mu,mr,mf [, da, db, z, fu, en]
	*/
	velocityTo: function(obj){
		this._stopIt(obj,"velocity");
		var x;
		if(!obj.tg.isShip){
			if(typeof obj.tg==='number') obj.tg = this.subs[obj.tg];
			else if(obj.tg.constructor.name==="Array") obj.tgv = new Vector3D(obj.tg);
		} else if(obj.tg.isInSpace) obj.tgv = obj.tg.position;
		if(obj.e>-1) x = this.subs[obj.e].velocity.magnitude();
		else x = this.ent.velocity.magnitude();
		if(x>1) obj.da = obj.da/x;
		var c = [this.ent,this.subs,0,this.defDelta,obj,this.cur[obj.e],0];
		this.fcb = addFrameCallback(function(delta){
			if(!delta || !c[0] || !c[0].isValid || c[6]) return;
			var f=1, bz=1, da=1, db=1, act, col, v, dist, mul,slow, check;
			if(c[3]>delta) f = 1/(c[3]/delta);
			else f = 1-(c[3]-delta);
			c[2] += delta;
			if(c[2]>=c[4].t) return;
			if(c[4].e>-1) act = c[1][c[4].e];
			else act = c[0];
			col = act.collisionRadius;
			if(c[4].tgv) v = c[4].tgv;
			else v = c[4].tg;
			if(c[4].tg.isInSpace) col += c[4].tg.collisionRadius;
			dist = act.position.distanceTo(v);
			if(dist<col*4){
				f *= 1/(col*4/dist);
				slow = 1;
			}
			if(dist<col){
				if(!c[6]){
					act.velocity = [0,0,0];
					c[6] = 1;
					if(c[4].fu) act.fuel = 0;
					if(c[4].en) act.energy = 0;
					c[5].mf = 0;
				}
				return;
			}
			if(c[4].z) bz = Math.sqrt(Math.abs(act.position.z));
			if(c[4].da && c[2]<c[4].da) da = 1-((c[4].da-c[2])/c[4].da);
			if(c[4].db && c[2]>c[4].t-c[4].db) db = (c[4].t-c[2])/c[4].db;
			mul = bz*da*db*f;
			if(c[4].mu) act.velocity = act.vectorUp.multiply(c[4].mu*mul);
			if(c[4].mr) act.velocity = act.vectorRight.multiply(c[4].mr*mul);
			if(c[4].mf){
				check = c[4].mf*mul;
				if(!slow && c[5].mf>check) check = c[5].mf;
				act.velocity = act.vectorForward.multiply(check);
				c[5].mf = check;
			}
			if(c[4].fu) act.fuel = (act.velocity.magnitude()/act.maxSpeed)*7;
			if(c[4].en) act.energy = (act.velocity.magnitude()/act.maxSpeed)*act.maxEnergy;
			return;
		});
		this._addTo("velocity",this.fcb,obj);
		return true;
	},
	/** e, velo
	*/
	setVelocity: function(obj){
		this._stopIt(obj,"velocity");
		if(obj.e>-1) this.ent.subEntities[obj.e].velocity = obj.velo;
		else this.ent.velocity = obj.velo;
		return true;
	},
	/** e
	*/
	stopVelocity: function(obj){
		this._stopIt(obj,"velocity");
		if(obj.e>-1) this.ent.subEntities[obj.e].velocity = [0,0,0];
		else this.ent.velocity = [0,0,0];
		return true;
	},
	/** e, t, st [,z, da, db]
	*/
	walk: function(obj){
		var c = [this.ent,this.subs,0,this.defDelta,obj];
		this.fcb = addFrameCallback(function(delta){
			if(!delta || !c[0] || !c[0].isValid) return;
			var f=1, bz=1, da=1, db=1, act, up,rol, mul, steps;
			if(c[3]>delta) f = 1/(c[3]/delta);
			else f = 1-(c[3]-delta);
			c[2] += delta;
			steps = Math.floor(c[2]);
			if(c[2]>=c[4].t || steps>=c[4].st) return;
			if(c[4].e>-1) act = c[1][c[4].e];
			else act = c[0];
			if(c[4].z) bz = Math.sqrt(Math.abs(act.position.z));
			if(c[4].da && c[2]<c[4].da) da = 1-((c[4].da-c[2])/c[4].da);
			if(c[4].db && c[2]>c[4].t-c[4].db) db = (c[4].t-c[2])/c[4].db;
			mul = Math.PI*bz*da*db*f;
			rol = Math.cos(c[2]*mul);
			up = Math.cos(c[2]*3*mul);
			act.position = act.position.subtract([rol*0.05,up*0.15,mul*0.15]);
			return;
		});
		this._addTo("walk",this.fcb,obj);
		return true;
	},
	walkTo: function(){},
	/** e
	*/
	stopWalk: function(obj){
		this._stopIt(obj,"walk");
		return true;
	},
	/** e, t, mz [, z, da, db]
	*/
	zoom: function(obj){
		var c = [this.ent,this.subs,0,this.defDelta,obj];
		this.fcb = addFrameCallback(function(delta){
			if(!delta || !c[0] || !c[0].isValid) return;
			var f=1, bz=1, da=1, db=1, act, mf=1;
			if(c[3]>delta) f = 1/(c[3]/delta);
			else f = 1-(c[3]-delta);
			c[2] += delta;
			if(c[2]>=c[4].t) return;
			if(c[4].e>-1) act = c[1][c[4].e];
			else act = c[0];
			if(c[4].z) bz = Math.sqrt(Math.abs(act.position.z));
			if(c[4].da && c[2]<c[4].da) da = 1-((c[4].da-c[2])/c[4].da);
			if(c[4].db && c[2]>c[4].t-c[4].db) db = (c[4].t-c[2])/c[4].db;
			if(c[4].mz>1) mf = 1+(da*db*bz*(c[4].mz%1));
			else mf = 1-(f*da*db*bz*(1%c[4].mz));
			act.position = act.position.multiply(mf);
			return;
		});
		this._addTo("zoom",this.fcb,obj);
		return true;
	},
	zoomTo: function(){},
	/** e
	*/
	stopZoom: function(obj){
		this._stopIt(obj,"zoom");
		return true;
	},
	/** e, p, v [, t, da, db]
	*/
	setProp: function(obj){
		if(!obj.t){
			if(obj.e>-1) this.subs[obj.e][obj.p] = obj.v;
			else this.ent[obj.p] = obj.v;
		} else {
			var c = [this.ent,this.subs,0,this.defDelta,obj];
			this.fcb = addFrameCallback(function(delta){
				if(!delta || !c[0] || !c[0].isValid) return;
				var f=1, da=1, db=1, act;
				if(c[3]>delta) f = 1/(c[3]/delta);
				else f = 1-(c[3]-delta);
				c[2] += delta;
				if(c[2]>=c[4].t) return;
				if(c[4].e>-1) act = c[1][c[4].e];
				else act = c[0];
				if(c[4].da && c[2]<c[4].da) da = 1-((c[4].da-c[2])/c[4].da);
				if(c[4].db && c[2]>c[4].t-c[4].db) db = (c[4].t-c[2])/c[4].db;
				if(typeof act[c[4].p]==='number') act[c[4].p] += c[4].v*f*da*db/c[4].t;
				else act[c[4].p] = act[c[4].p].multiply(c[4].v*f*da*db/c[4].t); // TODO check
				return;
			});
			this._addTo("props",this.fcb,obj);
		}
		return true;
	},
	/** e, mat, sha
	*/
	setMaterials: function(obj){
		var act;
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		if(obj.mat && obj.sha) act.setMaterials(obj.mat,obj.sha);
		else if(obj.mat) act.setMaterials(obj.mat,{});
		else if(obj.sha) act.setMaterials({},obj.sha);
		return true;
	},
	/** e, prop
	* i, p, v
	*/
	modifyMaterials: function(obj,prop){
		var m = this._getMaterials(obj),k = Object.keys(m),w;
		if(!k.length) return;
		for(var o=0;o<obj.prop.length;o++){
			w = obj.prop[o];
			m[k[w.i]][w.p] = w.v;
		}
		this._setMaterials(obj,m);
	},
	/** e, mat, tex, col
	*/
	highlight: function(obj){
		var act,m;
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		m = act.getMaterials();
		m[obj.mat].emission_map = obj.tex;
		m[obj.mat].emission_modulate_color = obj.col;
		act.setMaterials(m);
	},
	setTextures: function(obj){},
	/** e, sha [, rep]
	*/
	setShader: function(obj){
		if(obj.rep){
			var k = Object.keys(obj.sha),w,h,v;
			for(var o=0;o<k.length;o++){
				w = obj.sha[k[o]].uniforms;
				h = Object.keys(w);
				for(var i=0;i<h.length;i++){
					v = obj.sha[k[o]].uniforms[h[i]];
					if(v.value==="p1") v.value = clock.absoluteSeconds-1;
				}
			}
		}
		if(obj.e>-1) this.subs[obj.e].setShaders(obj.sha);
		else this.ent.setShaders(obj.sha);
		return true;
	},
	/** e, tex (Array), uni (Array), val (Array)
	*/
	setShaderProp: function(obj){
		var a,b,flagM,i;
		a = this._getShaders(obj);
		b = Object.keys(a);
		if(!b.length){
			a = this._getMaterials(obj);
			flagM = 1;
			b = Object.keys(a);
		}
		if(!b.length) return false;
		if(obj.tex) for(i=0;i<obj.tex.length;i++) a[b[0]].textures = obj.tex;
		if(obj.uni) for(i=0;i<obj.uni.length;i++) a[b[0]].uniforms[obj.uni[i]].value = obj.val[i];
		if(flagM){
			if(obj.e>-1) this.subs[obj.e].setMaterials(a);
			else this.ent.setMaterials(a);
		} else {
			if(obj.e>-1) this.subs[obj.e].setShaders(a);
			else this.ent.setShaders(a);
		}
		return true;
	},
	/** e, id, scale, pos
	*/
	explosion: function(obj){
		var act,sha = {};
		sha[obj.id] = {
			fragment_shader:"lib_ms_whexit.fs",
			vertex_shader:"lib_ms_bbT.vs",
			textures:["lib_explode2.png"],
			uniforms:{
				colorMap:{type:"texture",value:0},
				Time:{binding:"universalTime"},
				Dest:{binding:"destination",bindToSubentity:true,normalized:false}}};
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		act.destination = [2,clock.absoluteSeconds-1,obj.scale];
		act.setShaders(sha);
		if(obj.pos) act.position = obj.pos;
	},
	/** e, id, scale, pos
	*/
	hyper: function(obj){
		var act,sha = {};
		sha[obj.id] = {
			fragment_shader:"lib_ms_whexit.fs",
			vertex_shader:"lib_ms_bbT.vs",
			textures:["lib_hyper.png"],
			uniforms:{
				colorMap:{type:"texture",value:0},
				Time:{binding:"universalTime"},
				Dest:{binding:"destination",bindToSubentity:true,normalized:false}}};
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		act.destination = [6,clock.absoluteSeconds-1,obj.scale];
		act.setShaders(sha);
		if(obj.pos) act.position = obj.pos;
	},
	/** e, id, scale, pos, col
	*/
	sun: function(obj){
		var act,sha = {};
		sha[obj.id] = {
			fragment_shader:"lib_ms_sun.fs",
			vertex_shader:"lib_ms_bbT.vs",
			uniforms:{
				Suncolor:{type:"vector",value:obj.col},
				Time:{type:"float",value:0},
				Dest:{binding:"destination",bindToSubentity:true,normalized:false}}};
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		act.destination = [1,1,obj.scale];
		act.setShaders(sha);
		if(obj.pos) act.position = obj.pos;
	},
	/** e, id, scale, pos, col
	*/
	twinkle: function(obj){
		var act,sha = {};
		sha[obj.id] = {
			fragment_shader:"lib_ms_bg_twinkle.fs",
			vertex_shader:"lib_ms_bbT.vs",
			uniforms:{
				Time:{binding:"universalTime"},
				Dest:{binding:"destination",bindToSubentity:true,normalized:false}}};
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		act.destination = [99999999,0,obj.scale];
		act.setShaders(sha);
		if(obj.pos) act.position = obj.pos;
	},
	/** e, id, scale, pos, col
	*/
	moon: function(obj){
		var act,sha = {};
		sha[obj.id] = {
			fragment_shader:"lib_ms_moon.fs",
			vertex_shader:"lib_ms_moon.vs",
			textures:[{name:obj.tex,cube_map:true}],
			uniforms:{
				colorMap:{type:"texture",value:0},
				Suncolor:{type:"vector",value:obj.col},
				Scale:{type:"float",value:obj.scale}}};
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		act.setShaders(sha);
		if(obj.pos) act.position = obj.pos;
	},
	/** e, t, tg, [exh, fe, dist, swap]
	*/
	setBinding: function(obj){
		if(typeof obj.tg==='number'){
			if(obj.tg>-1) obj.tg = this.subs[obj.tg];
			else obj.tg = this.ent;
		}
		if(obj.dist){
			if(obj.e>-1) obj.distv = this.subs[obj.e].position.subtract(obj.tg.position);
			else obj.distv = obj.tg.position;
		}
		var c = [this.ent,this.subs,0,this.defDelta,obj];
		this.fcb = addFrameCallback(function(delta){
			if(!delta || !c[0] || !c[0].isValid) return;
			var f=1, act, p, o, s = 0;
			if(c[3]>delta) f = 1/(c[3]/delta);
			else f = 1-(c[3]-delta);
			c[2] += delta;
			if(c[2]>=c[4].t) return;
			if(c[4].e>-1) act = c[1][c[4].e];
			else act = c[0];
			if(c[4].fe){
				act.fuel = c[4].tg.fuel;
				act.energy = c[4].tg.energy;
			}
			if(c[4].exh){
				if(c[4].tg.desiredSpeed) s = c[4].tg.speed+1;
				act.fuel = (s/c[4].tg.maxSpeed)*0.7;
				act.energy = (s/c[4].tg.maxSpeed)*c[4].tg.maxEnergy;
			}
			if(c[4].dsor){
				act.destination = c[4].tg.position;
				act.energy = 0;
			}
			if(c[4].distv){
				o = c[4].tg.orientation;
				if(c[4].swap) o = o.rotate(c[4].tg.vectorUp,-Math.PI);
				act.orientation = o;
				p = c[4].tg.position.add(c[4].distv.rotateBy(o));
				act.position = p;
			}
			return;
		});
		this._addTo("bind",this.fcb,obj);
		return true;
	},
	/** e
	*/
	stopBinding: function(obj){
		this._stopIt(obj,"bind");
		return true;
	},
	/** e, pos
	*/
	setPosition: function(obj){
		if(obj.e>-1) this.subs[obj.e].position = obj.pos;
		else this.ent.position = obj.pos;
		return true;
	},
	/** e, tg, ex, p
	*/
	setExhaust: function(obj){
		if(obj.e>-1){
			this.subs[obj.e].position = this.subs[obj.tg].exhausts[obj.ex].position;
			this.subs[obj.e][obj.p] = this.subs[obj.tg].exhausts[obj.ex].size.multiply(0.11764); // .dat 1/8.5
		}
		return true;
	},
	/** e, tg [, off]
	*/
	setPositionTo: function(obj){
		var pos;
		if(obj.tg>-1) pos = this.subs[obj.tg].position;
		else pos = this.ent.position;
		if(obj.off) pos.add(obj.off);
		if(obj.e>-1) this.subs[obj.e].position = pos;
		else this.ent.position = pos;
		return true;
	},
	/** e, f, tg [, off]
	*/
	setFlasherPositionTo: function(obj){
		var pos;
		if(obj.tg>-1) pos = this.subs[obj.tg].position;
		else pos = this.ent.position;
		if(obj.off) pos = pos.add(obj.off);
		if(obj.e>-1) this.subs[obj.e].flashers[obj.f].position = pos;
		else this.ent.flashers[obj.f].position = pos;
		return true;
	},
	/** e, ori
	*/
	setOrientation: function(obj){
		if(obj.e>-1) this.subs[obj.e].orientation = obj.ori;
		else this.ent.orientation = obj.ori;
		return true;
	},
	/** e, inf
	*/
	snapIn: function(obj){
		this._stopIt(obj,"rotate");
		var act,ori;
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		ori = act.orientation;
		ori.w = this._snapIn(ori.w);
		ori.x = this._snapIn(ori.x);
		ori.y = this._snapIn(ori.y);
		ori.z = this._snapIn(ori.z);
		act.orientation = ori;
	},
	/** n
	*/
	setBackground: function(obj){
		this.bg.name = obj.n;
		setScreenBackground(this.bg);
	},
	/** mw, mh [, n]
	*/
	zoomBackground: function(obj){
		this.bg.mw = obj.mw;
		this.bg.mh = obj.mh;
		if(obj.n) this.bg.name = obj.n;
		this.fcb = addFrameCallback(this._zoomBG.bind(this));
		this.fcbs.push(this.fcb);
		this.flow.bg["-1"] = [this.fcb];
	},
	zoomBackgroundTo: function(){},
	/**
	*/
	stopBackground: function(){
		this._stopIt({e:-1},"bg");
	},
	/**
	*/
	clearBackground: function(){
		this.stopBackground();
		setScreenBackground(null);
	},
	resetBackground: function(obj){
		this.stopBackground();
		this.bg = {name:null,width:this.w,height:this.h,mw:1,mh:1};
		if(obj && obj.n) this.setBackground(obj);
	},
	/** n
	*/
	setOverlay: function(obj){
		this.ov.name = obj.n;
		setScreenOverlay(this.ov);
	},
	/** mw, mh [, n]
	*/
	zoomOverlay: function(obj){
		this.ov.mw = obj.mw;
		this.ov.mh = obj.mh;
		if(obj.n) this.ov.name = obj.n;
		this.fcb = addFrameCallback(this._zoomOV.bind(this));
		this.fcbs.push(this.fcb);
		this.flow.ov["-1"] = [this.fcb];
	},
	/**
	*/
	stopOverlay: function(){
		this._stopIt({e:-1},"ov");
	},
	/**
	*/
	clearOverlay: function(){
		this.stopOverlay();
		setScreenOverlay(null);
	},
	/** t
	*/
	fadeIn: function(obj){
		this._stopIt({e:-1},"ov");
		this.ov = {name:"lib_blend0.png",width:this.w,height:this.h,mw:1,mh:1};
		this.ovFade = {n:0,dir:1,t:obj.t};
		this.fcb = addFrameCallback(this._fadeOV.bind(this));
		this.fcbs.push(this.fcb);
		this.flow.ov["-1"] = [this.fcb];
	},
	/** t
	*/
	fadeOut: function(obj){
		this._stopIt({e:-1},"ov");
		this.ov = {name:"lib_blend10.png",width:this.w,height:this.h,mw:1,mh:1};
		this.ovFade = {n:0,dir:0,t:obj.t};
		this.fcb = addFrameCallback(this._fadeOV.bind(this));
		this.fcbs.push(this.fcb);
		this.flow.ov["-1"] = [this.fcb];
	},
	/** e, lv, x, y, mz
	*/
	screenCornerPos: function(obj){
		var pos,
			w = 1024/this.w,
			h = 768/this.h;
		if(obj.e>-1) pos = this.subs[obj.e].position;
		else pos = this.ent.position;
		pos.x = obj.lv*pos.z*obj.x;
		pos.y = obj.lv*pos.z*0.75*obj.y;
		if(obj.mz) pos.z *= obj.mz;
		else pos.z *= 1.3;
		var wh = w/h;
		pos = pos.add([w*wh-1,h*wh-1,0]);
		this.ent.position = pos;
		return true;
	},
	// Sub functions
	_addTo: function(w,id,obj){
		this.fcbs.push(id);
		if(this.flow[w][obj.e]) this.flow[w][obj.e].push(id);
		else this.flow[w][obj.e] = [id];
	},
	/** e, what
	*/
	_stopIt: function(obj,what){
		var f = this.flow[what][obj.e],ind;
		if(!f) return false;
		for(var i=0;i<f.length;i++){
			if(isValidFrameCallback(f[i])) removeFrameCallback(f[i]);
			ind = this.fcbs.indexOf(f[i]);
			if(ind!==-1) this.fcbs.splice(ind,1);
		}
		this.flow[what][obj.e] = [];
		return true;
	},
	/**
	*/
	_zoomBG: function(delta){
		if(!delta) return;
		this.bg.width *= this.bg.mw;
		this.bg.height *= this.bg.mh;
		setScreenBackground(this.bg);
	},
	/**
	*/
	_zoomOV: function(delta){
		if(!delta) return;
		this.ov.width *= this.ov.mw;
		this.ov.height *= this.ov.mh;
		setScreenOverlay(this.ov);
	},
	/**
	*/
	_fadeOV: function(delta){
		if(!delta) return;
		var f = Math.floor(this.ovFade.n*10/this.ovFade.t);
		if(this.ovFade.dir) f = 10-f;
		else f++;
		if(f<0) f = 0;
		if(f>10) f = 10;
		this.ov.name = "lib_blend"+f+".png";
		setScreenOverlay(this.ov);
		this.ovFade.n += delta;
	},
	_removeFCBs: function(){
		if(!this.fcbs.length) return false;
		for(var i=0;i<this.fcbs.length;i++){
			if(isValidFrameCallback(this.fcbs[i])) removeFrameCallback(this.fcbs[i]);
		}
		this.fcb = 0;
		this.fcbs = [];
		return true;
	},
	_getMaterials: function(obj){
		if(obj.e>-1) return this.subs[obj.e].getMaterials();
		return this.ent.getMaterials();
	},
	_setMaterials: function(obj,m){
		if(obj.e>-1) return this.subs[obj.e].setMaterials(m);
		return this.ent.setMaterials(m);
	},
	_getShaders: function(obj){
		if(obj.e>-1) return this.subs[obj.e].getShaders();
		return this.ent.getShaders();
	},
	_setPilot: function(obj){
		var act;
		this.setShaderProp(obj);
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		act.destination = obj.scpos;
		act.lightsActive = obj.init;
	},
	_pilotSpeak: function(obj){
		var act;
		if(obj.e>-1) act = this.subs[obj.e];
		else act = this.ent;
		act.energy = obj.fade;
		if(obj.txt) mission.addMessageText(obj.txt);
	},
	_sRND: function(seed,max){
		return ((0x731753*seed)>>16)%max;
	},
	_snapIn: function(e){
		if(e>0.9) e = 1;
		if(e>0.6 && e<0.8) e = 0.707107;
		if(e>0.4 && e<0.6) e = 0.5;
		if(e>-0.1 && e<0.1) e = 0.0;
		if(e>-0.6 && e<-0.4) e = -0.5;
		if(e>-0.8 && e<-0.6) e = -0.707107;
		if(e<-0.9) e = -1;
		return e;
	}
};
}).call(this);
Scripts/Lib_Animator.txt
Lib_Animator

The Animator is a helper for animations on missionscreens.

Methods:

_start(obj)
worldScripts.Lib_Animator._start(obj);

Required members:
obj.model		- String. Role
obj.flow		- Array. Animation flow object.

Optional members:
obj.background	- String.
obj.choices		- Object.
obj.choicesKey	- String.
obj.music		- String
obj.overlay		- String.
obj.screenID	- String.
obj.title		- String.

obj.fadeIn		- Bool. If used replaces obj.overlay.
obj.corner		- Object. See below.
obj.aPos		- Array.
obj.aOris		- Array.
obj.aBinds		- Array.
obj.aProps		- Array.
obj.bPos		- Array.
obj.bOris		- Array.
obj.bBinds		- Array.
obj.bProps		- Array.
obj.pilots		- Array. Sets texture, scale, position and display for character overlay.

obj.caller		- String. worldScript name for callback and checkpoints.
obj.callback	- String. Function to be called when user  
obj.checkpoint	- String. Function to be called when 'check' gets processed.
obj.custom		- Object. Holds custom Functions to be called when 'custom' gets processed.
obj.hud			- String.

obj.aSnd		- String.
obj.bSnd		- String.
obj.delta		- Number.


obj.flow
	
	Array containing Arrays with at least 2 members (frame and cmd).
		frame	- Number.
		cmd		- String. Action command.
		obj		- Object or String.
		subCMD	- Array.

Action commands:

reset
	Removes all Framecallbacks
clr
	Removes all Framecallbacks and resets 
clrMov
	Removes all object movement Framecallbacks (flight, rotate, speed, velocity, walk and zoom).
kill
	Removes mission.displayModel.

rotw
	Rotate entity in world space.
	e, t, rx,ry,rz [, z, da, db]
rot
	Rotate entity in model space
	e, t, rx,ry,rz [, z, da, db]
rotTo
	Rotate entity to target. Clears other rotations for this entity.
	e, t, tg [, z, da, db, s]
stopRot
	Clear rotations for this entity.
	e
fly
	e, t, mu,mr,mf, rx,ry,rz [,da, db, z, fu, en]
flyTo
	e, t, tg, s, mu,mr,mf [, da, db, z, fu, en]
stopFly
	e
KI
stopKI

spd
spdTo
stopSpd
velo
	e, t, mu,mr,mf [,da, db, z, fu, en]
veloTo
	e, t, tg, mu,mr,mf [, da, db, z, fu, en]
veloSet
	e, velo
stopVelo
	e
walk
	e, t, st [,z, da, db]
walkTo
	
stopWalk
	e
zoom
	e, t, mz [, z, da, db]
zoomTo
	
stopZoom
	e
prop
	e, t, p, v [, da, db]
matSet
	e, mat, sha
matMod
	e, prop
		i, p, v
tex
shadeSet
	e, sha, rep
shadeMod
	e, tex, uni, val
sun
	Replaces shader with textureless sun shader.
	e, id, scale, pos, col
moon
	Replaces shader with cubemapped moon shader.
	e, id, scale, pos, col
boom
	Replaces shader with explosion shader.
	e, id, scale, pos
hyper
	Replaces shader with hyperjump exit shader and plays sound.
	e, id, scale, pos
shoot
	Sets energy for laser shader and plays sound.
speak
	Sets energy for character overlay and plays sound or adds message text.
bind
	e, t, tg [,fe, dist, swap]
stopBind
	e
pos
	e, pos
posTo
	e, tg [, off]
posFl
	e, f, tg [, off]
ori
	e, ori
bg
	n
bgZoom
	mw, mh [, n]
bgZoomTo

bgStop
	none
bgClr
	none
ov
	n
ovZoom
	mw, mh [, n]
ovStop
	none
ovClr
	none
ovFadeIn
	t
ovFadeOut
	t
corner
	e, lv, x, y [, mz]
txt
	String.
snd1
	snd
snd2
	snd
mus
	snd
custom
	Calls custom function declared in the passed obj.custom.
	String.
goto
	Goto animation frame time.
	Number.
check
	Request action from obj.caller. Passes current frame time.
	If returned value is a Number goto frame.
	If no returned value (or false) stops the animation.
Scripts/Lib_BinSearch.js
/* jshint bitwise:false, forin:false */
/* global _lib_BinSearch */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_BinSearch";

/** This search tree uses two corresponding entries.
*	Must be instantiated! 
*	this.$myTree = new worldScripts.Lib_BinSearch._lib_BinSearch();
*/
this._lib_BinSearch = function(){this._lbs = null;};
_lib_BinSearch.prototype = {
	constructor: _lib_BinSearch,

	/** add() - Adds nodes to the search tree with the corresponding entry.
	@key - String. Add node with key.
	@val - Object. Corresponding entry
	*/
	add: function(key,val){
		var node = {key: key,left: null,right: null, val: val},cur;
		if(this._lbs === null) this._lbs = node;
		else {
			cur = this._lbs;
			while(true){
				if(key < cur.key){
					if(cur.left === null){
						cur.left = node;
						break;
					} else cur = cur.left;
				} else if(key > cur.key){
					if(cur.right === null){
						cur.right = node;
						break;
					} else cur = cur.right;
				} else break;
			}
		}
		return;
	},

	/** contains() - Returns corresponing entry or false.
	@key - String. Search for entry.
	@return - Object. Corresponding entry or false.
	*/
	contains: function(key){
		var found = false,cur = this._lbs;
		while(!found && cur){
			if(key < cur.key) cur = cur.left;
			else if(key > cur.key) cur = cur.right;
			else found = true;
		}
		if(!found) return false;
		return cur.val;
	},

	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._lbs);
	},

	/** size() - Returns the number of nodes in the search tree.
	@return - Number. Number of nodes.
	*/
	size: function(){
		var length = 0;
		this.traverse(function(node){length++;});
		return length;
	},

	/** toArray() - Returns an array containing all nodes in the search tree. The nodes are processed in-order.
	@return - Array. Entries.
	*/
	toArray: function(){
		var result = [];
		this.traverse(function(node){result.push(node.key,node.val);});
		return result;
	},

	/** toString() - Returns a comma-separated string consisting of all entries. The nodes are processed in-order.
	@separator - String. Optional. If specified separates the elements.
	@return - String. Concatenation of all entries.
	*/
	toString: function(separator){
		if(separator) return this.toArray().join(separator);
		else return this.toArray().toString();
	}
};
}).call(this);
Scripts/Lib_Config.js
/* jshint bitwise:false, forin:false */
/* global expandMissionText,log,mission,player,worldScripts */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_Config";
this.description = "Options for AddOns which ship configurable user-friendly features.";

this.$sets = {}; // Settings objects
this.$setNames = []; // Sorting
this.$setProbs = {}; // Error codes
this.$curSet = null;
this.$defCHC = {
	allowInterrupt: false,
	choices: {ZZZ: "Exit"},
	initialChoicesKey: null,
	message: "",
	screenID: this.name,
	textEntry: false,
	title: "Config"
};
this.$oxpc = {
	curBit: 0,
	curInd: 0,
	curKeys: [],
	curMSB: 0,
	curOpt: 0,
	curPage: 0,
	curTyp: 0,
	div: "",
	entry: 0,
	intern: 0,
	resort: 0,
	scr: ['Bool','SInt','EInt']
};
/** _registerSet(obj) - Use on .startUpComplete() or later.
	@obj - Settings object
	@return Number. Error code. 0 - ok, >0 - error (see missiontext.plist)
*/
this._registerSet = function(obj){
	if(this.startUp) return 99;
	if(!obj || typeof obj!=='object') return 101;
	if(!Object.keys(obj).length) return 102;
	var e = this._checkSet(obj);
	if(e) return e;
	var id = obj.Name+obj.Display,
		ind = this.$setNames.indexOf(id);
	this.$sets[id] = obj; // Store or update
	if(ind===-1){ // No double entries
		this.$setNames.push(id);
		this.$oxpc.entry++;
		this.$oxpc.resort = 1;
		this.$setProbs[id] = e;
	}
	return 0;
};
/** _unregisterSet(obj)
	@obj - Settings object
	@return Number. Error code. 0 - ok, >0 - error (see missiontext.plist)
*/
this._unregisterSet = function(obj){
	if(typeof obj!=='object') return 501;
	var id = obj.Name+obj.Display,
		ind = this.$setNames.indexOf(id);
	if(!this.$sets[id]) return 502;
	if(ind===-1) return 503;
	delete this.$sets[id];
	this.$oxpc.entry--;
	this.$oxpc.resort = 1;
	this.$setNames.splice(ind,1);
	return 0;
};
this._updSet = function(obj){
	if(typeof obj!=='object') return 601;
	var ind = -1,
		tmp,tmps,
		e = this._checkSet(obj);
	if(!e || e===211){
		ind = this.$setNames.indexOf(obj.Name+obj.Display);
		if(ind!==-1){
			tmp = this.$setNames[ind];
			if(tmp){
				if(!e){
					tmps = this._aid.objGrab(worldScripts[obj.Name],obj.Alive);
					this.$sets[tmp] = tmps[0][tmps[1]];
				}
				this.$curSet = this.$sets[tmp];
			}
		} else { // Not registered
			this.$curSet = obj;
			this.$oxpc.intern = 0;
			return e;
		}
	} else this.$curSet = obj;
	return e;
};
this.startUp = function(){
	delete this.startUp;
	this._aid = worldScripts.Lib_Main._lib; // Covered by manifest
	worldScripts.Lib_GUI.$IDRules.Lib_Config = {pic:1,mus:1};
	this.$oxpc.div = this._aid.scrToWidth("-",31,"-")+"\n";
};
this.startUpComplete = function(){
	delete this.startUpComplete;
	this.shipDockedWithStation(); // Implement interface
};
this.shipDockedWithStation = function(){
	if(!player.ship || !player.ship.isValid || !player.ship.docked) return;
	player.ship.dockedStation.setInterface(this.name,{
		title: "Config for AddOns",
		category: "AddOns",
		summary: this.description,
		callback: this._showStart.bind(this)
	});
};
this._checkSet = function(obj){
	var e = 0, t = ['string','function','object','undefined'], noti;
	if(typeof obj.Name!==t[0]) return 201;
	if(typeof obj.Display!==t[0]) return 202;
	if(typeof obj.Alive!==t[0]) return 203;
	if(obj.Alias && typeof obj.Alias!==t[0]) return 209;
	if(obj.Notify){
		if(typeof obj.Notify!==t[0]) return 204;
		noti = this._aid.objGrab(worldScripts[obj.Name],obj.Notify);
		if(typeof noti[0]==='undefined') return 212;
		if(typeof noti[0]!==t[1] && typeof noti[0][noti[1]]!==t[1]) return 212;
	}
	var k = this.$oxpc.scr,v;
	for(var i=0;i<k.length;i++){
		v = k[i];
		if(typeof obj[v]!==t[3]){
			if(typeof obj[v]!==t[2]) return 206+i;
			if(!obj[v] || !Object.keys(obj[v]).length) return 213+i;
		} else e++;
	}
	if(e===3) return 205;
	if(!this._checkAvail(obj)) return 211;
	return 0;
};
this._checkAvail = function(obj){
	var ws = worldScripts[obj.Name];
	if(!ws || ws.$deactivated) return false;
	var a = this._aid.objGrab(ws,obj.Alive);
	if(typeof a[0][a[1]]!=='object' || !Object.keys(a[0][a[1]]).length) return false;
	return true;
};
this._checkHeader = function(obj){
	var h = {Name:"-",Alias:0,Display:"-",Mani:"-",Author:"-",Copyright:"-",Licence:"-",Desc:"-",Version:"-",Ref:false,Extern:false},
		ws = worldScripts[obj.Name];
	h.Ref = this._checkAvail(obj);
	if(ws){
		if(ws.name) h.Name = ws.name;
		if(obj.Name) h.Name = obj.Name;
		if(ws.author) h.Author = ws.author;
		if(ws.copyright) h.Copyright = ws.copyright;
		if(ws.licence) h.Licence = ws.licence;
		else if(ws.license) h.Licence = ws.license;
		if(ws.description) h.Desc = ws.description;
		if(ws.version) h.Version = ws.version;
		if(ws.oolite_manifest_identifier) h.Mani = ws.oolite_manifest_identifier;
	} else if(obj.Name) h.Name = obj.Name;
	if(obj.Display) h.Display = obj.Display;
	if(obj.Alias) h.Alias = obj.Alias;
	if(!this.$oxpc.intern) h.Extern = true;
	return h;
};
this._checkValues = function(obj,typ){
	var wc = obj[typ],
		ws = worldScripts[obj.Name],
		max = 16777215, min = -16777215,
		c,ty,de,val;
	switch(typ){
		case 'Bool': ty = 'boolean'; de = 'string'; break;
		case 'SInt': ty = 'number'; de = 'string'; break;
		case 'EInt': ty = 'number'; de = 'object'; break;
	}
	for(var t in wc){
		if(!wc[t]) return 301;
		if(t==='Info' || t==='Notify') continue;
		c = wc[t];
		if(typeof c.Name!=='string') return 302;
		if(typeof c.Def!==ty) return 303;
		if(typeof c.Desc!==de) return 304;
		val = this._aid.objGet(ws,c.Name);
		if(typeof val!==ty) return 401;
		if(typ!=='Bool'){
			if(typeof c.Max!==ty) return 311;
			if(typeof c.Min!==ty) return 312;
			if(typ==='EInt') min = 0;
			if(c.Max<min) return 313;
			if(c.Max>max) return 314;
			if(c.Min>c.Max) return 315;
			if(c.Min<min) return 316;
			if(c.Def>c.Max) return 317;
			if(c.Def<c.Min) return 318;
			if(c.Def>max) return 319;
			if(c.Def<min) return 320;
			if(val>max || val>c.Max) return 411;
			if(val<min || val<c.Min) return 412;
		}
	}
	return 0;
};
this._fillOptions = function(chc,tit,ent){
	var obj = JSON.parse(JSON.stringify(this.$defCHC));
	if(chc) obj.choices = chc;
	if(tit) obj.title = tit;
	if(ent) obj.textEntry = true;
	return obj;
};
this._resort = function(){
	this.$setNames = this.$setNames.sort();
	this.$oxpc.resort = 0;
};
this._reset = function(){
	this.$curSet = null;
	this.$defCHC.initialChoicesKey = null;
	var o = this.$oxpc;
	o.curBit = 0;
	o.curMSB = 0;
	o.curInd = 0;
	o.curKeys = [];
	o.curOpt = 0;
	o.curPage = 0;
	o.curTyp = 0;
	o.intern = 0;
};
this._showScr = function(obj,mes,mesKey,cb){
	if(mesKey){
		obj.messageKey = mesKey;
		obj.message = null;
	} else if(mes) obj.message = mes;
	if(cb) mission.runScreen(obj,cb);
	else mission.runScreen(obj,this._choiceEval);
};
this._showStart = function(){
	var ch = {goLI:"List Settings",ZZZ:"Exit"},
		chc,txt,
		o = this.$oxpc;
	this._reset();
	if(!o.entry) ch = this._aid.scrChcUnsel(ch,'goLI');
	chc = this._fillOptions(ch);
	chc.exitScreen = "GUI_SCREEN_INTERFACES";
	this._showScr(chc,0,'LIBC_HEAD');
	txt = o.div;
	o.intern = 1;
	txt += "\nCurrently registered settings: "+o.entry+"\n\nChoose your option.";
	mission.addMessageText(txt);
	if(o.resort) this._resort();
};
this._showList = function(){
	var ch = {LINext:"Next Setting",lioOXP:"Select Setting",LIPNext:"Next page",LIPPrev:"Previous page",LIRet:"Back"},
		chc,txt,tmp,tmq,ind,
		o = this.$oxpc,
		max = o.curPage*10,
		e = o.entry;
	txt = this._aid.scrAddLine([["",1],["Settings",10],["Script/Alias",10],["Version",7],["Ref",3]],"","\n");
	txt += o.div;
	for(var i=0;i<10;i++){
		ind = i+max;
		if(e<=ind) break;
		tmq = this.$setNames[ind];
		tmp = this._checkHeader(this.$sets[tmq]);
		txt += this._aid.scrAddLine([[(o.curInd===ind?">":""),1]]);
		txt += this._addHeader(tmp,1);
	}
	if(e<=max) ch = this._aid.scrChcUnsel(ch,'LINext');
	if(e<=(1+o.curPage)*10) ch = this._aid.scrChcUnsel(ch,'LIPNext');
	if(!o.curPage) ch = this._aid.scrChcUnsel(ch,'LIPPrev');
	chc = this._fillOptions(ch);
	this._showScr(chc,txt);
};
/** _showOXPPage(obj) - for direct access
	@obj - Settings object
	@return Number. Error code. 0 - ok, >0 - error (see missiontext.plist)
*/
this._showOXPPage = function(obj){
	if(!obj || typeof obj!=='object') return 101;
	if(!Object.keys(obj).length) return 102;
	var e = this._updSet(obj),
		ch = {goBool:"Show switches",goEInt:"Show flags",goSInt:"Show values",OXPZZZ:"Back"},
		c = this.$curSet,
		o = this.$oxpc,
		tmp = this._checkHeader(c),
		chc,txt;
	if(e || !tmp.Ref){
		ch = this._aid.scrChcUnsel(ch,'goBool');
		ch = this._aid.scrChcUnsel(ch,'goEInt');
		ch = this._aid.scrChcUnsel(ch,'goSInt');
	} else {
		if(!c.Bool) ch = this._aid.scrChcUnsel(ch,'goBool');
		if(!c.EInt) ch = this._aid.scrChcUnsel(ch,'goEInt');
		if(!c.SInt) ch = this._aid.scrChcUnsel(ch,'goSInt');
		if(c.Reset) ch.OXPDef = "Reset to defaults";
	}
	txt = this._addHeader(tmp);
	txt += o.div;
	txt += this._aid.scrAddLine([["Author(s): "+tmp.Author,29],["Manifest: "+tmp.Mani,29],["Copyright: "+tmp.Copyright,29],["Licence: "+tmp.Licence,29],["Desc: "+tmp.Desc,29]],"\n");
	txt += o.div;
	if(e || !tmp.Ref) txt += "\n"+this._addError(tmp,e,'obj');
	else txt += "\nSettings object found.\n";
	chc = this._fillOptions(ch,tmp.Display);
	this._showScr(chc,txt);
	o.curBit = 0;
	o.curMSB = 0;
	o.curOpt = 0;
	o.curTyp = 0;
	return e;
};
/** _showOXPSub(obj) - for direct access
	@obj - Settings object
	@typ - String. Either "Bool","SInt" or "EInt"
	@return Number. Error code. 0 - ok, >0 - error (see missiontext.plist)
*/
this._showOXPSub = function(obj,typ){
	if(!obj || typeof obj!=='object') return 101;
	if(!Object.keys(obj).length) return 102;
	if(!typ || this.$oxpc.scr.indexOf(typ)===-1) return 701;
	var e = this._updSet(obj),
		c = this.$curSet,
		ch = {OXPNext:"Next option",OXPToggle:"Change current",OXPZZZ:"Back"},
		o = this.$oxpc,
		r,tp,val,inf,
		z = 0, sh = 1;
	o.curTyp = typ;
	var op = Object.keys(c[typ]),
		opl = op.length,
		opk = [],
		pass = this._checkValues(c,typ),
		tmp = this._checkHeader(c),
		txt = this._addHeader(tmp);
	if(e || pass) sh = 0;
	txt += o.div;
	if(sh){
		for(var x=0;x<opl;x++){
			tp = op[x];
			if(tp==='Info' || tp==='Notify') continue;
			if(opk.length>8) break;
			if(!c[typ][tp].Hide) opk.push(tp);
		}
		o.curKeys = opk;
		if(!opk.length){
			txt += "Nothing to configure yet.\n";
			z++;
		} else {
			for(var t in c[typ]){
				if(z>8) break;
				if(t==='Info' || t==='Notify') continue;
				val = c[typ][t];
				if(!val || val.Hide) txt += this._aid.scrAddLine([["",1],["-",1]],"","\n");
				else {
					if(!o.curOpt) o.curOpt = t;
					r = this._calcs(val);
					switch(typ){
						case 'Bool':
							txt += this._aid.scrAddLine([[(o.curOpt===t?">":" "),1],[z,1],[r.Dec,4],["D:"+val.Def,5],[val.Desc,8]],"","\n");
							break;
						case 'SInt':
							txt += this._aid.scrAddLine([[(o.curOpt===t?">":""),1],[z,1],[r.Dec,5],["D:"+r.Def,5],["R:"+r.Min+" - "+r.Max,10],[val.Desc,8]],"","\n");
							break;
						case 'EInt':
							o.curMSB = r.msb;
							txt += this._aid.scrAddLine([["",1],[z,10],[r.Dec,5],[r.str,14],[r.msb,0]],"","\n");
							for(var i=0;i<r.msb;i++){
								if(i && 0===i%3) txt += "\n";
								txt += this._aid.scrAddLine([[(o.curBit===i?">":""),1],[(r.revstr.length>i?r.revstr[i]:"0"),1],[(val.Desc.length>i?val.Desc[i]:" "),8]],"");
							}
							txt += "\n";
							z = Math.ceil(r.msb/3);
							break;
					}
				}
				z++;
			}
		}
	} else {
		if(e) txt += this._addError(tmp,e,typ);
		if(pass) txt += this._addError(tmp,pass,typ);
		z += (e?1:0)+(pass?1:0);
	}
	txt += this._aid.scrFillLines(z,9);
	if(c[typ].Info){
		inf = c[typ].Info;
		if(inf[0]==="^") inf = expandMissionText(inf.substr(1));
		txt += "\nDesc:\n"+inf.toString().substr(0,240)+"\n";
	}
	if(!sh || (opk.length<2 && typ!=='EInt')) ch = this._aid.scrChcUnsel(ch,'OXPNext');
	if(!sh || !opk.length) ch = this._aid.scrChcUnsel(ch,'OXPToggle');
	var chc = this._fillOptions(ch,tmp.Display);
	this._showScr(chc,txt);
	return e;
};
this._calcs = function(val){
	var s = this._aid.objGet(worldScripts[this.$curSet.Name],val.Name);
	var r = {Dec: s};
	switch(this.$oxpc.curTyp){
		case 'SInt':
			r.Dec = r.Dec;
			r.Def = val.Def;
			r.Max = val.Max;
			r.Min = val.Min;
			break;
		case 'EInt':
			r.str = this._aid.toBase(r.Dec,2);
			r.msb = this._aid.getMSB(val.Max);
			r.revstr = this._aid.strRev(r.str);
			break;
	}
	return r;
};
this._showOXPValueEnter = function(){
	var c = this.$curSet,
		o = this.$oxpc,
		tmp = this._checkHeader(c),
		txt = this._addHeader(tmp)+o.div,
		val = c[o.curTyp][o.curOpt],
		r = this._calcs(val);
	txt += this._aid.scrAddLine([["",1],["",1],[r.Dec,5],["D:"+r.Def,5],["R:"+r.Min+" - "+r.Max,10],[val.Desc,0]],"","\n");
	txt += o.div;
	txt += "\nHexadecimal whole numbers, e.g. '0xff'.";
	if(val.Min<0) txt += "\nNegative values, e.g. dec '-32' or hexadecimal '-0x20'.";
	if(val.Float) txt += "\nFloating point values , e.g '1.1'.";
	txt += "\n\nEnter your value.";
	var chc = this._fillOptions({},tmp.Name,1);
	this._showScr(chc,txt,null,this._choiceEnter);
};
this._addHeader = function(tmp,flag){
	return this._aid.scrAddLine([[tmp.Display,10],[(tmp.Alias?tmp.Alias:tmp.Name),10],[tmp.Version,4],[(flag?"":"#"),1],[(flag?"":""),1],[(flag?"":(tmp.Extern?"Ex":"")),1],[(tmp.Ref?"":"!")+(tmp.Extern?"-":String(this.$setProbs[tmp.Name+tmp.Display])),2.5]],"","\n");
};
this._addError = function(w,e,typ){
	this.$setProbs[w.Name+w.Display] = e;
	log(this.name,"Error: "+w.Name+" "+w.Display+" - ("+typ+") "+expandMissionText('LIBC_E'+e));
	return "Error: "+e+" - ("+typ+") "+expandMissionText('LIBC_E'+e)+"\n";
};
this._notiError = function(s,w){
	log(this.name,"Notification failed for "+s+"."+w+"!");
};
this._choiceEval = function(choice){worldScripts.Lib_Config._choices(choice); return;};
this._choices = function(choice){
	if(choice) this.$defCHC.initialChoicesKey = choice;
	var o = this.$oxpc,
		c = this.$curSet,
		p = Math.floor(o.curInd/10),
		cur = this.$setNames[o.curInd],
		a,b,flag,ind,ok,ws;
	var pp = p*10;
	switch(choice){
		case 'goBool':
			this._showOXPSub(c,'Bool');
			break;
		case 'goEInt':
			this._showOXPSub(c,'EInt');
			break;
		case 'goLI':
			o.intern = 1;
			this._showList();
			break;
		case 'lioOXP':
			this._showOXPPage(this.$sets[cur]);
			break;
		case 'goSInt':
			this._showOXPSub(c,'SInt');
			break;
		case 'LINext':
			o.curInd++;
			if(o.curInd>=pp+10 || o.curInd<pp || o.curInd>=o.entry) o.curInd = pp;
			this._showList();
			break;
		case 'LIPNext':
			o.curPage++;
			o.curInd = pp+10;
			this._showList();
			break;
		case 'LIPPrev':
			if(o.curPage>-1){
				o.curPage--;
				o.curInd = pp-10;
			}
			this._showList();
			break;
		case 'LIRet':
			this._showStart();
			break;
		case 'OXPDef':
			ws = worldScripts[c.Name];
			if(c.Notify) a = 1;
			ind = o.scr;
			for(var i=0;i<3;i++){
				flag = ind[i];
				if(c[flag]){
					if(!a && c[flag].Notify) b = 1;
					else b = 0;
					for(var j in c[flag]){
						if(typeof c[flag][j]!=='object') continue;
						this._aid.objSet(ws,c[flag][j].Name,c[flag][j].Def);
						if(!a && !b && c[flag][j].Notify) this._aid.objPass(ws,c[flag][j].Notify,j);
					}
					if(b) this._aid.objPass(ws,c[flag].Notify,flag);
				}
			}
			if(a) this._aid.objPass(ws,c.Notify,"All");
			this._showOXPPage(this.$sets[cur]);
			mission.addMessageText("Reset to defaults.");
			break;
		case 'OXPNext':
			switch(o.curTyp){
				case 'Bool':
				case 'SInt':
					ind = o.curKeys.indexOf(o.curOpt)+1;
					if(ind>o.curKeys.length-1) ind = 0;
					o.curOpt = o.curKeys[ind];
					break;
				case 'EInt':
					o.curBit++;
					if(o.curBit>=o.curMSB) o.curBit = 0;
					break;
			}
			this._showOXPSub(c,o.curTyp);
			break;
		case 'OXPToggle':
			ws = worldScripts[c.Name];
			ind = c[o.curTyp][o.curOpt].Name;
			switch(o.curTyp){
				case 'Bool':
					a = this._aid.objGet(ws,ind);
					a = !a;
					this._aid.objSet(ws,ind,a);
					flag = 1;
					break;
				case 'SInt':
					this._showOXPValueEnter();
					break;
				case 'EInt':
					a = this._aid.objGet(ws,ind);
					b = Math.pow(2,o.curBit);
					if(c[o.curTyp][o.curOpt].OneOf) a = b;
					else if(c[o.curTyp][o.curOpt].OneOfZero){
						if(a===b) a = 0;
						else a = b;
					} else {
						if(a&b) a -= b;
						else a += b;
					}
					this._aid.objSet(ws,ind,a);
					flag = 1;
					break;
			}
			if(flag){
				if(c[o.curTyp][o.curOpt].Notify){
					ok = this._aid.objPass(ws,c[o.curTyp][o.curOpt].Notify,o.curOpt);
					if(!ok) this._notiError(c.Name,c[o.curTyp][o.curOpt].Notify);
				}
				this._showOXPSub(c,o.curTyp);
			}
			break;
		case 'OXPZZZ':
			ws = worldScripts[c.Name];
			if(o.curTyp){
				if(c[o.curTyp].Notify){
					ok = this._aid.objPass(ws,c[o.curTyp].Notify,o.curTyp);
					if(!ok) this._notiError(c.Name,c[o.curTyp].Notify);
				}
				this._showOXPPage(c);
			} else {
				if(c.Notify){
					ok = this._aid.objPass(ws,c.Notify,"All");
					if(!ok) this._notiError(c.Name,c.Notify);
				}
				this.$curSet = null;
				if(o.intern){
					this.$defCHC.initialChoicesKey = 'LIRet';
					this._showList();
				} else this._reset(); // Bye
			}
			break;
		case 'ZZZ':
			this._reset();
			break;
	}
};
// Numerical input
this._choiceEnter = function(choice){worldScripts.Lib_Config._choiceEnterB(choice); return;};
this._choiceEnterB = function(choice){
	var o = this.$oxpc,
		c = this.$curSet,
		ws = worldScripts[c.Name],
		ind = c[o.curTyp][o.curOpt],
		sign = 1,
		ok,val;
	if(choice){
		// Store sign
		if(choice[0]==='-'){
			choice = choice.substr(1);
			sign = -1;
		}
		if(choice.length>2 && choice.substr(0,2)==="0x" && !isNaN(parseInt(choice))) val = parseInt(choice);
		else if(ind.Float && !isNaN(parseFloat(choice))) val = parseFloat(choice);
		else if(!isNaN(parseInt(choice))) val = parseInt(choice);
		if(typeof(val)!=='undefined'){
			val *= sign; // Reapply sign
			val = this._aid.clamp(val,ind.Min,ind.Max);
			this._aid.objSet(ws,ind.Name,val);
			if(c[o.curTyp][o.curOpt].Notify){
				ok = this._aid.objPass(ws,c[o.curTyp][o.curOpt].Notify,o.curOpt);
				if(!ok) this._notiError(c.Name,c[o.curTyp][o.curOpt].Notify);
			}
		}
	}
	this._showOXPSub(c,o.curTyp);
};
}).call(this);
Scripts/Lib_Crypt.js
/* jshint bitwise:false, forin:false */
/* global log */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_Crypt";

/** _decrypt() - Decrypts string.
	@str - String. Minimum length 6 chars.
	@pwd - String. Password. Minimum length 4 chars.
	@return - String/Boolean. String or false.
	Author: Terry Yuen.
*/
this._decrypt = function(str,pwd){
	if(!str || !pwd || str.length<6 || pwd.length<4){log(this.name,"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 j=0;j<str.length;j+=2){
		enc_chr = parseInt(parseInt(str.substring(j,j+2),16)^Math.floor((prand/modu)*255),null);
		dec_str += String.fromCharCode(enc_chr);
		prand = (mult*prand+incr)%modu;
	}
	return dec_str;
};

/** _encrypt() - Encrypts string.
	@str - String. Minimum length 6 chars.
	@pwd - String. Password. Minimum length 4 chars.
	@return - String/Boolean. String or false.
	Author: Terry Yuen.
*/
this._encrypt = function(str,pwd){
	if(!str || !pwd || str.length<6 || pwd.length<4){log(this.name,"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(this.name,"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 j=0;j<str.length;j++){
		enc_chr = parseInt(str.charCodeAt(j)^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;
};

/** _getCRC() - Returns simple checksum. Limits every char via &0xff and result via &0x3fff.
*/
this._getCRC = function(str){
	var crc = 0, i = str.length;
	while(i--) crc += (str.charCodeAt(i)&0xff);
	return crc&0x3fff;
};

/** _rot5() - Number rotation. Can be paired with _rot13.
*/
this._rot5 = function(str){
	return (str+'').replace(/[0-9]/g,function(s){return String.fromCharCode(s.charCodeAt(0)+(s<'5'?5:-5));});
};

/** _rot13() - Alphabet rotation. Can be paired with _rot5.
*/
this._rot13 = function(str){
	return (str+'').replace(/[a-z]/gi,function(s){return String.fromCharCode(s.charCodeAt(0)+(s.toLowerCase()<'n'?13:-13));});
};

/** _rot513 - Combined rot5 and rot13.
*/
this._rot513 = function(str){
	var a = this._rot5(str),b = this._rot13(a);
	return b;
};

/** _rot47() - Expanded rotation.
*/
this._rot47 = function(a,b){return++b?String.fromCharCode((a=a.charCodeAt()+47,a>126?a-94:a)):a.replace(/[^ ]/g,this._rot47);};

/** _Vigenere
* input   String.
* key     String. Alphabetical key.
* forward Bool. Set to true for decryption.
* $return String.
*/
this._Vigenere = function(input, key, forward){
	var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
		adjusted_key = "", i, key_char, output = "", key_index = 0, in_tag = false;
	if(key===null) key = "";
	key = key.toUpperCase();
	var key_len = key.length;
	for(i=0;i<key_len;i++){
		key_char = alphabet.indexOf(key.charAt(i));
		if(key_char<0) continue;
		adjusted_key += alphabet.charAt(key_char);
	}
	key = adjusted_key;
	key_len = key.length;
	if (key_len===0){
		key = "a";
		key_len = 1;
	}
	var input_len = input.length;
	for(i=0;i< input_len;i++){
		var input_char = input.charAt(i);
		if(input_char==="<") in_tag = true;
		else if(input_char===">") in_tag = false;
		if(in_tag){
			output += input_char;
			continue;
		}
		var input_char_value = alphabet.indexOf(input_char);
		if(input_char_value<0){
			output += input_char;
			continue;
		}
		var lowercase = input_char_value >= 26 ? true : false;
		if(forward) input_char_value += alphabet.indexOf(key.charAt(key_index));
		else input_char_value -= alphabet.indexOf(key.charAt(key_index));
		input_char_value += 26;
		if(lowercase) input_char_value = input_char_value % 26 + 26;
		else input_char_value %= 26;
		output += alphabet.charAt(input_char_value);
		key_index = (key_index + 1) % key_len;
	}
	return output;
};
}).call(this);
Scripts/Lib_Cubecode.js
/* global mission,worldScripts,SoundSource */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_Cubecode";

/** _initCubes
* @obj.pass - Number
* @obj.card - Texture
* @obj.ws - String
* @obj.path - String
*/
this._initCubes = function(obj){
	if(this.$cub.init) return false;
	var ret,map = "lib_lock.png",lo = 0,ws=null,wsf=null;
	if(obj){
		if(obj.card) map = obj.card;
		if(typeof obj.pass==="number") lo = obj.pass;
		if(obj.ws && obj.path){
			ws = obj.ws;
			wsf = obj.path;
		}
	}
	this.$cub.lock = lo;
	this.$cub.ws = ws;
	this.$cub.path = wsf;
	this.$head.subTex[0].mat["lib_null.png"].emission_map = map;
	this.$cub.code = -1;
	this.$cub.init = 1;
	ret = this._showCubes();
	return ret;
};
this.startUp = function(){
	this.$head = {
		model: "lib_ms_cubes",
		title: "Security check",
		background: "lib_console_bg.png",
		choices: {NEXT:"Next",ROTX:"Rotate X",ROTY:"Rotate Y",ROTZ:"Rotate Z",ZZZ:"QUIT"},
		text: "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n                                        Select your option:",
		caller: this.name,
		callback: "_choiceEval",
		checkpoint: "_checkpoints",
		aOris: [{e:-1,ori:[1,0,0,0]}],
		aPos: [{e:-1,pos:[0,30,250]}],
		subTex: [{e:0,mat:{"lib_null.png":{emission_map:"lib_lock.png"}}}],
		flow:[]
	};
	this.$cub = {
		code:-1,
		lock:0,
		cur:1,
		init:0,
		ws:null,
		path:null,
		curOri:[[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0]],
		flows:{a:[4,"rotX",{e:1,t:2,deg:90,da:1,db:1,inf:1}],b:[4,"rotY",{e:1,t:2,deg:90,da:1,db:1,inf:1}],c:[4,"rotZ",{e:1,t:2,deg:90,da:1,db:1,inf:1}]}
	};
	this.$snd = new SoundSource();
	this._aid = worldScripts.Lib_Main._lib;
};
this._showCubes = function(fl,w){
	var obj = this._aid.objClone(this.$head),ac,c = 0,ret,i,un = ['ROTX','ROTY','ROTZ','ZZZ'],m = "lib_ms_cube_a.png";
	for(i=1;i<10;i++){obj.aOris.push({e:i,ori:this.$cub.curOri[i]});}
	if(w) obj.replace = 1;
	else {
		obj.fadeIn = true;
		obj.flow.push([c,"ovFadeIn",{t:1.5}]);
		c++;
		obj.flow.push([c,"snd1",{snd:"lib_enter_code.ogg"}]);
		c++;
	}
	if(this.$cub.lock===this.$cub.code){
		for(i=0;i<4;i++) obj.choices = this._aid.scrChcUnsel(obj.choices,un[i]);
		obj.flow.push([c,"clr"]);
		c++;
		for(i=1;i<10;i++){
			obj.flow.push([c,"lit",{e:i,mat:m,tex:m,col:[0,0.6,0,1]}]);
			c++;
		}
	} else {
		obj.flow.push([c,"lit",{e:this.$cub.cur,mat:m,tex:m,col:[0,0.6,0,1]}]);
		c++;
		if(fl){
			ac = this.$cub.flows[fl];
			ac[0] = c;
			ac[2].e = this.$cub.cur;
			obj.flow.push(ac);
			c++;
			obj.flow.push([c,"check"]);
			c+=5;
			obj.flow.push([c,"snap",{e:this.$cub.cur}]);
			c++;
			obj.flow.push([c,"check"]);
			c++;
		}
	}
	c+=5;
	obj.flow.push([c,"clr"]);
	ret = worldScripts.Lib_Animator._start(obj);
	if(ret && !w && !fl) this._getRes();
	return ret;
};
this._choiceEval = function(choice){worldScripts.Lib_Cubecode._delayChoice(choice); return;};
this._delayChoice = function(choice){
	var cb,ws;
	switch(choice){
		case "NEXT":
			this.$cub.cur++;
			if(this.$cub.cur>9) this.$cub.cur = 1;
			if(this.$cub.lock===this.$cub.code){
				cb = 1;
				this.$cub.init = 0;
			} else this._showCubes(0,1);
			break;
		case "ROTX": this._showCubes("a",1); break;
		case "ROTY": this._showCubes("b",1); break;
		case "ROTZ": this._showCubes("c",1); break;
		case "ZZZ": cb = 1; this.$cub.init = 0; break;
	}
	if(cb && this.$cub.ws && this.$cub.path){
		ws = worldScripts[this.$cub.ws];
		this._aid.objPass(ws,this.$cub.path,this.$cub.code);
	}
	this.$snd.sound = "[changed-option]";
	this.$snd.play();
};
this._checkpoints = function(n){
	var o;
	switch(n){
		case 2:
			o = worldScripts.Lib_Animator._a;
			if(o) this.$cub.curOri[this.$cub.cur] = o.cur[this.$cub.cur].tOri;
			break;
		case 8:
			this._getRes();
			if(this.$cub.lock===this.$cub.code){
				this.$snd.sound = "lib_code_verified.ogg";
				this.$snd.play();
				this._showCubes(0,1);
			}
			break;
	}
	return true;
};
this._getRes = function(){
	var res = 0,pri = [3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61],a,b,c = mission.displayModel;
	for(var i=1;i<10;i++){
		a = pri[i];
		b = c.subEntities[i].vectorUp;
		if(b.x>0) res += b.x*11*a;
		if(b.y>0) res += b.y*13*a;
		if(b.z>0) res += b.z*17*a;
		b = c.subEntities[i].vectorRight;
		if(b.x>0) res += b.x*19*a;
		if(b.y>0) res += b.y*23*a;
		if(b.z>0) res += b.z*29*a;
		b = c.subEntities[i].vectorForward;
		if(b.x>0) res += b.x*31*a;
		if(b.y>0) res += b.y*37*a;
		if(b.z>0) res += b.z*41*a;
	}
	this.$cub.code = Math.floor(res);
};
}).call(this);
Scripts/Lib_EntityStrength.js
/* global system,worldScripts */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_EntityStrength";

this.$weapons = ["EQ_WEAPON_NONE","EQ_WEAPON_MINING_LASER","EQ_WEAPON_PULSE_LASER","EQ_WEAPON_BEAM_LASER","EQ_WEAPON_MILITARY_LASER","EQ_WEAPON_THARGOID_LASER"];
this.$slotsA = ["forwardWeapon","aftWeapon","portWeapon","starboardWeapon"];
this.$slotsB = ["weaponPositionForward","weaponPositionAft","weaponPositionPort","weaponPositionStarboard"];
/** Returns strength value for passed entity.
*/
this._check = function(ent){
	var w = ent.weaponFacings,
		eq = ent.equipment,
		esc = ent.escorts,
		sec = ent.subEntityCapacity,
		c = 0, wc = 0, ind = 0,
		i, l, s, t, tt;
	c += ent.maxEnergy*(ent.maxPitch+ent.maxRoll+ent.maxYaw)*ent.energyRechargeRate;
	if(ent.isPlayer) c += (ent.maxAftShield*ent.aftShieldRechargeRate+ent.maxForwardShield*ent.forwardShieldRechargeRate)*0.5;
	c += ent.maxSpeed*ent.maxThrust;
	c *= 0.001;
	if(eq && eq.length){
		if(ent.isPlayer) c += eq.length;
		else c += eq.length*5;
	}
	if(esc && esc.length){
		l = esc.length;
		for(i=0;i<l;i++) c += this._check(esc[i])*0.5;
	}
	c += ent.maxEscorts;
	if(w){
		for(i=0;i<4;i++){
			t = ent[this.$slotsA[i]];
			if(t && t.equipmentKey){
				ind = this.$weapons.indexOf(t.equipmentKey);
				if(ind<0) ind = 6;
				tt = ent[this.$slotsB[i]];
				if(tt && tt.length) ind += tt.length; // multi
				wc += ind;
			}
		}
		if(ent.accuracy>1) wc *= ent.accuracy;
		c += wc*10;
	}
	if(sec){
		s = ent.subEntities;
		l = (s?s.length:0);
		for(i=0;i<l;i++) if(s[i].isTurret) c += 50;
		if(ent.isPlayer && l<sec) c += 50*(sec-l); // no cheats
	}
	c += ent.missileCapacity*5;
	if(!ent.maxSpeed) c /= ent.mass*0.000005;
	if(ent.isThargoid) c *= 1.5;
	c = Math.round(c);
	// Add value to ship script.
	if(ent.script) ent.script.$libStrength = c;
	return c;
};
/** Returns maximum value of NPC ships in the system.
	Params:
		rel - Piloted ships relative to entity.
		all - Return Array of all numbers.
		redo - Set script values. Do not use careless.
*/
this._gather = function(rel,all,redo){
	var a = [],b,l,i=0,c;
	if(rel) b = system.filteredEntities(this,function(e){return(e.isShip && e.isValid && e.isPiloted && !e.isPlayer);},rel,52000);
	else {
		b = system.allShips;
		i = 1;
	}
	l = b.length;
	if(l>400) l = 400; // Cap
	if(redo){
		for(i;i<l;i++) a.push(this._check(b[i]));
	} else {
		for(i;i<l;i++){
			if(b[i].script){
				if(typeof b[i].script.$libStrength==='number') c = b[i].script.$libStrength;
				else c = this._check(b[i]);
			} else c = -1;
			a.push(c);
		}
	}
	if(!all){
		if(a.length) a = Math.max.apply(Math,a);
		else a = 0;
		if(!isFinite(a)) a = 999999;
		if(!rel) worldScripts.Lib_Main._lib.$entLastStrength = a;
	}
	return a;
};
this.shipSpawned = function(ent){
	this._check(ent);
	if(ent.primaryRole==="lib_test" && ent.script.name!=="lib_test") worldScripts.Lib_Main._lib.$entCstRemoved = true;
};
this.missionScreenOpportunity = function(){this._gather(0,0,1); delete this.missionScreenOpportunity;};
this.shipWillDockWithStation = this.shipExitedWitchspace = function(){this._gather(0,0,1);};
}).call(this);
Scripts/Lib_GUI.js
/* global addFrameCallback,galaxyNumber,guiScreen,mission,missionVariables,player,removeFrameCallback,setScreenBackground,setScreenOverlay,worldScripts,SoundSource,System */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_GUI";
this.copyright = "(C)2016-2018";
this.description = "GUI images and sounds handling.";

// Full Sets
this.$guis = {};
this.$IDs = {};
// Extension - temporary overrides, FIFO
this.$guisExt = {
	GUI_SCREEN_EQUIP_SHIP: [],
	GUI_SCREEN_GAMEOPTIONS: [],
	GUI_SCREEN_INTERFACES: [],
	GUI_SCREEN_KEYBOARD: [],
	GUI_SCREEN_LOAD: [],
	GUI_SCREEN_LONG_RANGE_CHART: [],
	GUI_SCREEN_MANIFEST: [],
	GUI_SCREEN_MARKET: [],
	GUI_SCREEN_MARKETINFO: [],
	GUI_SCREEN_OPTIONS: [],
	GUI_SCREEN_REPORT: [],
	GUI_SCREEN_SAVE: [],
	GUI_SCREEN_SAVE_OVERWRITE: [],
	GUI_SCREEN_SHIPLIBRARY: [],
	GUI_SCREEN_SHIPYARD: [],
	GUI_SCREEN_SHORT_RANGE_CHART: [],
	GUI_SCREEN_STATUS: [],
	GUI_SCREEN_STICKMAPPER: [],
	GUI_SCREEN_STICKPROFILE: [],
	GUI_SCREEN_SYSTEM_DATA: []
};
// Extension
this.$IDsExt = {};
// screenID rules - expand, but do not change!
this.$IDRules = {
	"oolite-contracts-cargo-none": {pic:1,ov:1,snd:1,mus:1},
	"oolite-contracts-cargo-details": {mpic:1,snd:1,mus:1},
	"oolite-contracts-cargo-summary": {pic:1,ov:1,snd:1,mus:1},
	"oolite-contracts-parcels-none": {pic:1,ov:1,snd:1,mus:1},
	"oolite-contracts-parcels-details": {mpic:1,snd:1,mus:1},
	"oolite-contracts-parcels-summary": {pic:1,ov:1,snd:1,mus:1},
	"oolite-contracts-passengers-none": {pic:1,ov:1,snd:1,mus:1},
	"oolite-contracts-passengers-details": {mpic:1,snd:1,mus:1},
	"oolite-contracts-passengers-summary": {pic:1,ov:1,snd:1,mus:1},
	"oolite-primablemanager": {pic:1,ov:1,snd:1,mus:1},
	"oolite-register": {pic:1,ov:1,snd:1,mus:1}
};
this.$ambi = {
	audio: true,
	guiFX: true,
	ex: 0,
	last: 0,
	reinit: 0,
	crowd: "generic",
	generic: [],
	redux: [],
	none: []
};
this.$noEx = Object.keys(this.$IDRules);

this.startUp = function(){
	this._aid = worldScripts.Lib_Main._lib;
	for(var i=0;i<this.$noEx.length;i++) this._aid.objLock(this.$IDRules[this.$noEx[i]]);
	this._aid.objLock(this.$IDRules);
};
// remove missionVariables.LIB_GUI sometime
this.startUpComplete = function(){
	delete this.startUpComplete;
	var gu,ms = missionVariables.LIB_GUI,ind=0,max=0,mv=missionVariables.LIB_GUI_CONFIG,mvp;
	this.$s = new SoundSource();
	this.$amb = new SoundSource();
	this.$op = null;
	this.$cur = null;
	this.$k = Object.keys(this.$guis);
	if(ms && worldScripts[ms]) this.$cur = ms;
	if(mv){
		mvp = JSON.parse(missionVariables.LIB_GUI_CONFIG);
		if(mvp.cur && worldScripts[mvp.cur]) this.$cur = mvp.cur;
		this.$ambi.audio = mvp.audio;
		this.$ambi.guiFX = mvp.guiFX;
	}
	gu = this.$k;
	if(!this.$cur && gu.length) this.$cur = gu[gu.length-1];
	if(gu && gu.length){
		ind = gu.indexOf(this.$cur);
		this.$E0 = 0;
		if(ind!==-1){
			ind = Math.pow(2,ind);
			this.$E0 = ind;
		}
		max = Math.pow(2,gu.length)-1;
		this.$inf = {Name:"Lib_GUI",Alias:"Library",Display:"GUI-Config",Alive:"$inf",
			Bool:{
				B0:{Name:"$ambi.audio",Def:true,Desc:"Ambience Audio"},
				B1:{Name:"$ambi.guiFX",Def:true,Desc:"SFX on GUIs"}
			},
			EInt:{E0:{Name:"$E0",Def:ind,Min:0,Max:max,Desc:gu,Notify:"_chgSet",OneOfZero:1},Info:"Choose your background set."}};
		worldScripts.Lib_Config._registerSet(this.$inf);
	}
	// TODO: Attempt to avoid JS-reset bug - remove when solved.
	setScreenBackground("lib_black.png");
	this._setCrowd(player.ship.dockedStation);
	this._stationAmb();
	missionVariables.LIB_GUI = null;
};
this._chgSet = function(){
	var gu = this.$k, ind = this._aid.getMSB(this.$E0)-1;
	if(ind<0) this.$cur = null;
	else this.$cur = gu[ind];
};
this.playerWillSaveGame = function(){
	var o = {
		cur: this.$cur,
		audio: this.$ambi.audio,
		guiFX: this.$ambi.guiFX
	};
	missionVariables.LIB_GUI_CONFIG = JSON.stringify(o);
};
this.alertConditionChanged = function(){
	if(player.ship.isInSpace) this.guiScreenChanged();
};
this.gameResumed = function(){
	this.guiScreenChanged();
	this._stationAmb();
};
this.gamePaused = function(){
	this._stationAmb(1);
};
this.shipDied = function(){
	if(this.$guiFCB) removeFrameCallback(this.$guiFCB);
	this._stationAmb(1);
};
this.shipDockedWithStation = function(st){
	this._setCrowd(st);
	this._stationAmb();
};
this._setCrowd = function(st){
	var inf, p = ["generic","redux","none"];
	if(st.scriptInfo && st.scriptInfo.lib_crowd && p.indexOf(st.scriptInfo.lib_crowd)>-1) inf = st.scriptInfo.lib_crowd;
	if(!st.isMainStation && !st.hasNPCTraffic && !inf) this.$ambi.crowd = "none";
	else {
		if(inf) this.$ambi.crowd = inf;
		else if(st.name==="Rock Hermit") this.$ambi.crowd = "redux";
		else this.$ambi.crowd = "generic";
	}
};
this._stationAmb = function(s){
	if(player.ship.isInSpace || s){
		if(this.$AmbTimer) this.$AmbTimer.stop();
		this.$amb.stop();
		this.$ambi.last = 0;
	} else {
		if(!this.$ambi.audio) return;
		if(this.$ambi.ex){
			this.$ambi.last = 0;
			if(this.$AmbTimer){
				this.$AmbTimer.stop();
				this.$AmbTimer.nextTime = clock.absoluteSeconds+1;
				this.$AmbTimer.start();
			}
			return;
		}
		var w = this.$ambi[this.$ambi.crowd], i = Math.floor(Math.random()*w.length), d,r;
		if(w.length){
			this.$amb.sound = w[i].name;
			this.$amb.volume = w[i].vol;
			this.$amb.play();
			d = w[i].dur-1;
			if(this.$AmbTimer) this.$AmbTimer.stop();
			else this.$AmbTimer = new Timer(this,this._stationAmb,-1,d);
			this.$AmbTimer.nextTime = clock.absoluteSeconds+d;
			this.$AmbTimer.start();
			this.$ambi.reinit = 0;
			this.$ambi.last = clock.absoluteSeconds+d;
		}
	}
};
this.shipWillLaunchFromStation = function(){
	this.$s.stop();
	this._stationAmb(1);
};
this.guiScreenChanged = function(){
	var gu = guiScreen, amb;
	switch(gu){
		case "GUI_SCREEN_EQUIP_SHIP":
		case "GUI_SCREEN_LONG_RANGE_CHART":
		case "GUI_SCREEN_MANIFEST":
		case "GUI_SCREEN_MARKET":
		case "GUI_SCREEN_MARKETINFO":
		case "GUI_SCREEN_REPORT":
		case "GUI_SCREEN_SHIPYARD":
		case "GUI_SCREEN_SHORT_RANGE_CHART":
		case "GUI_SCREEN_STATUS":
			this.$op = null;
			this._setGUI(gu,0);
			this.$ambi.ex = 0;
			break;
		case "GUI_SCREEN_SYSTEM_DATA":
			var inf = System.infoForSystem(galaxyNumber,player.ship.infoSystem),sp = 0;
			if(inf.sun_gone_nova || inf.sun_going_nova) sp = 1;
			this.$op = null;
			this._setGUI(gu,sp);
			this.$ambi.ex = 0;
			break;
		case "GUI_SCREEN_INTERFACES":
		case "GUI_SCREEN_SHIPLIBRARY":
			this.$op = gu;
			this._setGUI(gu,0);
			this.$ambi.ex = 0;
			break;
		case "GUI_SCREEN_GAMEOPTIONS":
		case "GUI_SCREEN_KEYBOARD":
		case "GUI_SCREEN_LOAD":
		case "GUI_SCREEN_OPTIONS":
		case "GUI_SCREEN_SAVE":
		case "GUI_SCREEN_SAVE_OVERWRITE":
		case "GUI_SCREEN_STICKMAPPER":
		case "GUI_SCREEN_STICKPROFILE":
			this.$op = gu;
			this._setGUI(gu,0);
			this._stationAmb(1);
			amb = 1;
			break;
		case "GUI_SCREEN_MISSION":
			this.$op = null;
			if(mission.screenID) this._setMS(mission.screenID);
			else {
				this._stationAmb(1);
				this.$ambi.reinit = 1;
				amb = 1;
			}
			if(mission.exitScreen) this.$op = gu;
			break;
		case "GUI_SCREEN_MAIN":
			amb = 1;
			this.$op = null;
			break;
		default:
			this.$op = null;
	}
	if(this.$op){
		if(!this.$guiFCB) this.$guiFCB = addFrameCallback(this._checkGUI.bind(this));
	} else {
		if(this.$guiFCB) removeFrameCallback(this.$guiFCB);
		delete this.$guiFCB;
	}
	if(!amb && !this.$ambi.reinit && this.$ambi.last<=clock.absoluteSeconds+2) this._stationAmb();
	if(amb) this.$ambi.ex = 1;
};
this.missionScreenEnded = function(){
	if(this.$ambi.reinit) this._stationAmb();
};
this._checkGUI = function(){
	if(!player.ship.isValid) return;
	if(this.$op && guiScreen!==this.$op && guiScreen!=="GUI_SCREEN_MISSION") this.guiScreenChanged();
};
this._setGUI = function(gui,spc){
	if(!this.$cur && !this.$guisExt[gui].length){
		setScreenBackground("lib_black.png");
		return;
	}
	var g = this.$guis[this.$cur],
		ex = this.$guisExt[gui],
		big = player.ship.hudAllowsBigGui,
		img,ov,ref,snd,sou,w;
	if(!ex.length && (!g || (!g[gui] && !g.generic))) return;
	if(ex.length) w = ex[0];
	else if(g[gui]) w = g[gui];
	else w = g.generic;
	if(player.ship.isInSpace){
		sou = "sndF";
		if(spc && w.picNova) img = "picNova";
		else if(w.picFlight) img = "picFlight";
		else if(w.pic) img = "pic";
	} else {
		sou = "snd";
		if(spc && w.picNova) img = "picNova";
		else if(player.ship.dockedStation.script.$guiVoid && w.picVoid){
			img = "picVoid";
			sou = null;
		} else if(w.pic) img = "pic";
	}
	if(sou){
		if(w[sou]) snd = w[sou];
		else if(w.sndRef){
			ref = this.$guis[w.sndRef];
			if(ref && ref[gui] && ref[gui][sou]) snd = ref[gui][sou];
		}
	}
	if(img){
		if(player.alertCondition===3 && w[img+"Red"]) img = img+"Red";
		if(big && w[img+"Big"]) img = img+"Big";
		this._applyBG(w[img]);
	}
	if(w.ov){
		ov = "ov";
		if(player.alertCondition===3 && w[ov+"Red"]) ov = ov+"Red";
		if(big && w[ov+"Big"]) ov = ov+"Big";
		this._applyOV(w[ov]);
	}
	if(snd && this.$ambi.guiFX) this.$s.playSound(snd);
	this.$guisExt[gui].shift();
};
this._setMS = function(id){
	if(!this.$cur && !this.$IDsExt[id]) return;
	var m = this.$IDRules,
		s = this.$IDs[this.$cur],
		ex = (this.$noEx.indexOf(id)===-1?this.$IDsExt[id]:null),
		big = player.ship.hudAllowsBigGui,
		sou = "snd",
		img,ov,w,ref;
	if(!m[id] || (!ex && !s && (!s[id] && !s.generic))){
		this.$s.stop();
		this._stationAmb(1);
		this.$ambi.reinit = 1;
		this.$ambi.ex = 1;
		return;
	}
	if(ex) w = ex;
	else if(s[id]) w = s[id];
	else w = s.generic;
	if(player.ship.isInSpace) sou = "sndF";
	if(m[id].snd && this.$ambi.guiFX){
		if(w[sou]) this.$s.playSound(w[sou]);
		else if(w.sndRef){
			ref = this.$IDs[w.sndRef];
			if(ref && ref[id] && ref[id][sou]) this.$s.playSound(ref[id][sou]);
		} else this.$s.stop();
	} else this.$s.stop();
	if(m[id].mpic && w.mpic) ov = "mpic";
	else {
		if(m[id].pic && w.pic) img = "pic";
		if(m[id].ov && w.ov) ov = "ov";
	}
	if(img){
		if(big && w.picBig) img = "picBig";
		this._applyBG(w[img]);
	}
	if(ov){
		if(big && w[ov+"Big"]) ov = ov+"Big";
		this._applyOV(w[ov]);
	}
};
this._applyBG = function(img){
	if(typeof img==="object" && img.scale){
		var sc = this._aid.ooImageScale(img.x,img.y,img.zoom);
		setScreenBackground({name:img.name,height:sc.height,width:sc.width});
	} else setScreenBackground(img);
};
this._applyOV = function(ov){
	if(typeof ov==="object" && ov.scale){
		var sc = this._aid.ooImageScale(ov.x,ov.y,ov.zoom);
		setScreenOverlay({name:ov.name,height:sc.height,width:sc.width});
	} else setScreenOverlay(ov);
};
}).call(this);
Scripts/Lib_Main.js
/* jshint bitwise:false, forin:false */
/* global _libMain,defaultFont,expandMissionText,galaxyNumber,global,log,missionVariables,oolite,player,system,worldScripts,Ship,System */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 - Helper functions and data objects */
(function(){
"use strict";
this.name = "Lib_Main";
this.author = "Svengali";

/** _libMain - Main class. There's often no need to instantiate it for other AddOns.
* Just create a reference -> this._aid = worldScripts.Lib_Main._lib;
*/
this._libMain = function(){};
_libMain.prototype = {
	constructor: _libMain,
	/** Contains connected systems based on players position when
	* the game starts or player jumps to a new galaxy.
	*/
	$connections: [[],[],[],[],[],[],[],[]],
	$missionActive: null,
	/** Contains data keys of all ships.
	*/
	$ships: [],
	spc: String.fromCharCode(31),
	spcw: defaultFont.measureString(String.fromCharCode(31)),
	spcs: String.fromCharCode(32),
	spcsw: defaultFont.measureString(String.fromCharCode(32)),
	// Sanity
	$entCstChanged: [],
	$entCstPatched: false,
	$entCstRemoved: false,
	$entLastStrength: 0,

	// ***** Number *****
	/** Returns clamped Number in range min...max.
	*/
	clamp: function(n,min,max){
		return((n>max)?max:(n<min)?min:n);
	},
	/** Counts set bits in Number.
	*/
	cntBits: function(n){
		var res = 0;
		if(n<0) n = ~n;
		while(n>0){
			if((n&1)) res++;
			n>>=1;
		}
		return res;
	},
	/** Returns greatest common denominator.
	*/
	gcd: function(x,y){
		return(y===0?x:this.gcd(y,x%y));
	},
	/** Returns position of most significant bit of non-negative Number or zero.
	*/
	getMSB: function(n){
		var res = 15;
		for(var r=8;r>0;r>>=1) res = (n<1<<res)?res-r:res+r;
		return((n<1<<res)?res:res+1);
	},
	/** Modulo. Differs from JS native for negative values.
	* -2%3 = -2 while this._aid.mod(-2,3) = 1
	*/
	mod: function(x,y){
		return x-Math.floor(x/y)*y;
	},
	/** Returns random Number in range 0...n.
	*/
	rand: function(n){
		return n*(Math.random()%1) | 0;
	},
	/** Returns random Number in range min...max.
	*/
	randXY: function(min,max){
		return min+this.rand(max-min+1);
	},
	/** Returns String with converted base of Number.
	* this._aid.toBase(-29,16) = "-1d"
	*/
	toBase: function(n,bas,from){
		return parseInt(n,from || 10).toString(bas);
	},
	/** Returns String with converted base of Number. Programmers version.
	* this._aid.toBaseSign(-29,16) = "-0xffffe3"
	*/
	toBaseSign: function(n,bas){
		return (n<0?"-":"")+(bas===16?"0x":"")+(parseInt((n<0?(0xffffffffffff+n)+1:n),10)&0xffffff).toString(bas);
	},
	/** Returns String with a leading zero if n<10.
	*/
	toLZ: function(n){
		return((n!==null&&n<10&&n>=0?"0":"")+n);
	},
	/** Returns String with a leading zeros if n<100.
	*/
	toLZZ: function(n){
		return(n!==null&&n<100&&n>=0?"0"+this.toLZ(n):""+n);
	},
	/** Returns String with specified length filled with leading zeros.
	*/
	toNLZ: function(n,len){
		return ("0000000000000000"+n).substr(-len);
	},
	/** Returns rounded Number with specified precision.
	*/
	toPrec: function(n,p){
		if(!n) return 0;
		var y = Math.pow(10,p);
		return Math.round(n*y)/y;
	},

	// ***** String *****
	/** Returns reverted String.
	*/
	strRev: function(str){
		if(!str) return "";
		var i = str.length, res = "";
		while(i--) res += str[i];
		return res;
	},
	/** Returns random String with specified length.
	*/
	strRND: function(len,chars){
		chars = chars || 'abcdefghijklmnopqrstuvwxyz';
		var res = '', rndPos;
		for(var i=0;i<len;i++){
			rndPos = Math.floor(Math.random()*chars.length);
			res += chars.substring(rndPos,rndPos+1);
		}
		return res;
	},
	/** Returns random numbered String with specified length.
	*/
	strRNDInt: function(len){
		var res = String(1000000000+Math.random()*1000000000 | 0);
		return res.substr(0,len);
	},
	/** Returns trimmed String. Unlike .trim() it also removes control chars.
	*/
	strTrim: function(str){
		return str.replace(/[\f\r\n\t\v]/g,"").trim();
	},

	// ***** Array *****
	/** Merges nested Arrays
	*/
	arrConcat: function(arrA,arrB){
		if(this.typeGet(arrB)!=="array") return arrA;
		var res = arrA.slice(0), na = arrB.slice(0);
		var la = res.length, lb = na.length;
		for(var i=0;i<lb;i++){
			if(i>la-1) break;
			if(this.typeGet(na[i])==="array") res[i] = res[i].concat(na[i]);
		}
		return res;
	},
	/** Returns difference between Arrays. Strict equality.
	*/
	arrDiff: function(a,b,oneway){
		var res = [], i = a.length;
		while(i--) if(b.indexOf(a[i])===-1) res.push(a[i]);
		if(oneway) return res;
		i = b.length;
		while(i--) if(a.indexOf(b[i])===-1) res.push(b[i]);
		return res;
	},
	/** Returns flattened Array.
	*/
	arrFlat: function(arr){
		return this._arrFlatten(arr,[]);
	},
	/** Returns Array with min and max of flat input Array containing only numbers.
	*/
	arrMinMax: function(arr){
		return [Math.min.apply(Math,arr),Math.max.apply(Math,arr)];
	},
	/** Returns Array without specified element. Strict equality.
	*/
	arrOmit: function(arr,ele){
		var len = arr.length, res = arr.slice(0);
		for(var i=0;i<len;i++){
			if(res[i]===ele) res.splice(i,1);
		}
		return res;
	},
	/** Returns shuffled Array.
	*/
	arrShuffle: function(arr){
		var j,k,t, res = arr.slice(0);
		j = res.length;
		do{
			k = (Math.random()*(j--))<<0;
			t = res[j];
			res[j] = res[k];
			res[k] = t;
		} while(j);
		return res;
	},
	/** Returns sorted Array of Objects. Selectionsort.
	*/
	arrSortBy: function(arr,prop){
		var res = arr.slice(0), len = res.length, min,i,j;
		for(i=0;i<len;i++){
			min = i;
			for(j=i+1;j<len;j++){
				if(j>0 && j<len && res[j][prop]<res[min][prop]) min = j;
			}
			if(i!==min) this._arrSwap(res,i,min);
		}
		return res;
	},
	/** Merges Arrays.
	*/
	arrUnion: function(){
		var len = arguments.length, res = [];
		while(len--){
			var arg = arguments[len];
			for(var i=0;i<arg.length;i++){
				var ele = arg[i];
				if(res.indexOf(ele)===-1) res.push(ele);
			}
		}
		return res;
	},
	/** Returns Array with unique entries. Strict equality.
	*/
	arrUnique: function(arr){
		var res = arr.slice(0), len = res.length, i = -1;
		while(i++<len){
			var j = i+1;
			for(;j<res.length;++j){
				if(res[i]===res[j]) res.splice(j--,1);
			}
		}
		return res;
	},

	// ***** Object *****
	/** Return deep cloned Object.
	*/
	objClone: function(obj){
		if(typeof obj!=='object' || obj===null) return obj;
		var res = obj.constructor();
		for(var i in obj) res[i] = this.objClone(obj[i]);
		return res;
	},
	/** Deep freeze Object. In-place.
	*/
	objFreeze: function(obj){
		var prop, propKey;
		Object.freeze(obj);
		for(propKey in obj){
			prop = obj[propKey];
			if(!obj.hasOwnProperty(propKey) || typeof prop!=="object" || Object.isFrozen(prop)) continue;
			this.objFreeze(prop);
		}
	},
	/** Returns value of the Objects member in the specified path or null.
	*/
	objGet: function(obj,path){
		var a = this.objGrab(obj,path);
		if(!a[0] || !a[1]) return null;
		return a[0][a[1]];
	},
	/** Returns Array with Object and key following the specified path.
	*/
	objGrab: function(obj,path){
		var p = path.split("."), len = p.length-1, last = p.pop(), o = obj;
		if(len<1) return [obj,last];
		for(var i=0;i<len;i++) o = this._objRet(o,p[i]);
		return [o,last];
	},
	/** Lock object, but leave expandable.
	*/
	objLock: function(obj){
		var props = Object.getOwnPropertyNames(obj);
		for(var i=0;i<props.length;i++){
			var desc = Object.getOwnPropertyDescriptor(obj,props[i]);
			if("value" in desc) desc.writable = false;
			desc.configurable = false;
			Object.defineProperty(obj,props[i],desc);
		}
	},
	/** Returns merged object. In-place.
	*/
	objMerge: function(target,obj,omit){
		for(var key in obj){
			if(!this._objHasOwn(obj,key)) continue;
			if(omit && !this._objHasOwn(target,key)) continue;
			var val = obj[key];
			if(typeof target[key]==="object"){
				if(!target[key]) target[key] = this.objClone(val);
				else target[key] = this.objMerge(target[key] || {},val);
			} else target[key] = this.objClone(val);
		}
		return target;
	},
	/** Returns deep merged object. In-place.
	*/
	objMergeDeep: function(orig,objects){
		if(typeof orig!=="object") orig = {};
		var len = arguments.length, target = this.objClone(orig);
		for(var i=1;i<len;i++){
			var val = arguments[i];
			if(typeof val==="object") this.objMerge(target,val);
		}
		return target;
	},
	/** Passes value to Function in Object following the specified path. Returns true or false.
	*/
	objPass: function(obj,path,val){
		var a = this.objGrab(obj,path);
		if(!a[0] || !a[1]) return false;
		if(typeof a[0][a[1]]==='function'){
			a[0][a[1]](val);
			return true;
		}
		return false;
	},
	/** Passes value to Function in Object following the specified path. Returns returned value.-)
	*/
	objRequest: function(obj,path,val){
		var a = this.objGrab(obj,path);
		if(!a[0] || !a[1]) return false;
		if(typeof a[0][a[1]]==='function') return a[0][a[1]](val);
		return false;
	},
	/** Deep seal Object. In-place.
	*/
	objSeal: function(obj){
		var prop, propKey;
		Object.seal(obj);
		for(propKey in obj){
			prop = obj[propKey];
			if(!obj.hasOwnProperty(propKey) || typeof prop!=="object" || Object.isSealed(prop)) continue;
			this.objSeal(prop);
		}
	},
	/** Sets value in Object following the specified path. Returns true or false.
	*/
	objSet: function(obj,path,val){
		var a = this.objGrab(obj,path);
		if(!a[0] || !a[1]) return false;
		if(!a[1]){
			obj[path] = val;
			return true;
		}
		a[0][a[1]] = val;
		return true;
	},

	// ***** Type *****
	/** Returns lowercase type of Argument for JS native and Oolites classes.
	*/
	typeGet: function(obj){
		var t = typeof obj;
		switch(t){
			case 'undefined':
			case 'boolean':
			case 'number':
			case 'string':
			case 'function': return t;
		}
		if(!obj) return null;
		t = Object.prototype.toString.call(obj).substr(8).replace("]","");
		return t.toLowerCase();
	},

	// ***** AddOn *****
	/** Checks required worldScripts and versions. The Array req is expected to contain two elements
	* (name and version) per check and version must be null for legacy scripts or if you want to
	* check only for existance!
	* Note: Checks property '$deactivated' in scripts.
	*/
	addOnVersion: function(who,req,quiet){
		var ok,ws, len = req.length, res = [];
		for(var i=0;i<len;i+=2){
			ok = 1;
			ws = worldScripts[req[i]];
			if(typeof ws==='undefined') ok = 0;
			else if(ws.$deactivated || (req[i+1] && !this.cmpVersion(ws.version,req[i+1]))) ok = 0;
			if(!ok){
				if(!quiet){
					if(!req[i+1]) this._addOnVersion(who,req[i],null);
					else this._addOnVersion(who,req[i],req[i+1]);
				}
			}
			res.push(ok);
		}
		return res;
	},
	/** Clears specified missionVariables.
	*/
	clrMVs: function(arr){
		var i = 0, l = arr.length;
		for(i;i<l;i++) missionVariables[arr[i]] = null;
		return;
	},
	/** Delete specified properties.
	*/
	clrProps: function(where,arr){
		var i = 0, l = arr.length;
		for(i;i<l;i++) delete worldScripts[where][arr[i]];
		return;
	},
	/** Compares dotted version Strings. Strips pre-/suffixes in first argument.
	* Returns 0 if smaller, 1 if equal and 2 if bigger.
	* JS native .localeCompare() may be another option.
	*/
	cmpVersion: function(act,exp){
		var x = act.replace(/[^\d\.]/g,"").split('.'),y = exp.split('.');
		if(!x.length) return 0;
		for(var i=0;i<x.length;i++){
			if(i>y.length-1 || x[i]>y[i]) return 2;
			if(x[i]<y[i]) return 0;
		}
		return 1;
	},

	// ***** Screens *****
	/** Returns aligned String.
	* e.g. this._aid.scrAddLine([ ["First",8],["Second",8],["Third",8] ],"","\n");
	*/
	scrAddLine: function(line,sep,brk){
		if(!line){
			if(brk) return brk;
			return "";
		}
		var le = line.length,res = "",coun = 0,tmp;
		for(var i=0;i<le;i++){
			if(coun>30) break;
			tmp = this.scrToWidth(line[i][0],line[i][1],"",1);
			res += tmp[0]+(sep?sep:"");
			coun += tmp[1];
			if(sep==="\n") coun = 0;
		}
		if(brk) res += brk;
		return res;
	},
	/** Sets unselectable flag for missionscreen choices. In-place.
	*/
	scrChcUnsel: function(opt,key){
		if(typeof opt[key]==='string') opt[key] = {text:opt[key],unselectable:true};
		else if(typeof opt[key]==='object') opt[key].unselectable = true;
		return opt;
	},
	/** Returns String with linebreaks up to max.
	*/
	scrFillLines: function(cur,max){
		var res = "";
		for(var i=cur;i<max;i++) res += "\n";
		return res;
	},
	/** Returns String with specified length in em.
	* Truncated if width > max, otherwise filled up with space or chr.
	*/
	scrToWidth: function(str,max,chr,ret,rgt){
		if(typeof str==='object' && str) str = str.toSource();
		str = String(str);
		var l = defaultFont.measureString(str),c,d;
		if(max<1){
			if(ret) return [str,l];
			return str;
		}
		if(chr) c = defaultFont.measureString(chr);
		if(l>max){
			while(l>max){
				if(str && str.length>1) str = str.substr(0,str.length-1);
				d = defaultFont.measureString(str);
				l = d;
			}
		}
		if(l<max){
			while(l<max){
				if(chr && c<max-l){
					str += chr;
					l += c;
				} else {
					if(max-l>this.spcsw){
						if(rgt) str = this.spcs+str;
						else str += this.spcs;
						l += this.spcsw;
					} else {
						if(max-l>this.spcw){
							if(rgt) str = this.spc+str;
							else str += this.spc;
							l += this.spcw;
						} else break;
					}
				}
			}
		}
		if(ret) return [str,l];
		return str;
	},

	// ***** Oolite *****
	/** Returns Object with width, height, gcd and ratio of screen window.
	*/
	ooScreen: function(){
		var g = this._ooGame(),w,h,d,a;
		w = g.gameWindow.width;
		h = g.gameWindow.height;
		d = this.gcd(w,h);
		a = w/h;
		return {width:w,height:h,gcd:d,ratio:a};
	},
	/** Returns shader support level.
	*/
	ooShaders: function(){
		var g = this._ooGame();
		if(g.wireframeGraphics) return 0;
		switch(g.detailLevel){
			case 'DETAIL_LEVEL_MINIMUM':
			case 'DETAIL_LEVEL_NORMAL': return 0;
			case 'DETAIL_LEVEL_SHADERS': return 1;
			case 'DETAIL_LEVEL_EXTRAS': return 2;
			default: return 0;
		}
	},
	/** Returns Object with width and height to be used for images
	* x and y are width and height in pixels, zoom any number or 0.
	* Author: Norbert Nagy.
	*/
	ooImageScale: function(x,y,zoom){
		var he = 480, wi = 0, o = this.ooScreen();
		if(x>0 && y>0){
			if(o.ratio>=x/y){
				if(zoom<0) wi = 0;
				else {
					if(zoom) he = 0;
					wi = o.ratio*480;
				}
			} else if(zoom<0){
				he = 0;
				wi = o.ratio*480;
			} else if(!zoom) wi = o.ratio*480;
		}
		if(!wi && he>0) wi = null;
		else if(wi>0 && !he) he = null;
		return {height:he,width:wi};
	},

	// ***** Entities *****
	/** Spawns VisualEffect in front of (or behind) the player.
	*/
	entFXCreate: function(ent,mul){
		return system.addVisualEffect(ent,player.ship.position.add(player.ship.vectorForward.multiply(mul)));
	},
	/** Sets main texture for entity.
	*/
	entSetMainTex: function(ent,tex){
		var ta = ent.getMaterials(), tb = Object.keys(ta), tc = ta[tb[0]].textures;
		if(!tc) return false;
		if(typeof tc[0] === 'string') ta[tb[0]].textures[0] = tex;
		else ta[tb[0]].textures[0].name = tex;
		ent.setMaterials(ta);
	},
	setMaterials: function(ent,obj){
		if(obj.mat && obj.sha) ent.setMaterials(obj.mat,obj.sha);
		else if(obj.mat) ent.setMaterials(obj.mat,{});
		else if(obj.sha) ent.setMaterials({},obj.sha);
		return true;
	},

	// ***** Helper *****
	_addOnVersion: function(w,r,v){
		log("Check",w+" requirement not matched : "+r+(v?" required min. version "+v:"")+" not found.");
		return;
	},
	_arrFlatten: function(arr,res){
		var len = arr.length, i = -1;
		while(len--){
			var cur = arr[++i];
			if(Array.isArray(cur)) this._arrFlatten(cur,res);
			else res.push(cur);
		}
		return res;
	},
	_arrSwap: function(arr,f,s){
		var temp = arr[f];
		arr[f] = arr[s];
		arr[s] = temp;
	},
	_chkGlobal: function(){
		var a = Object.getOwnPropertyNames(global), warn=[], t, ok = expandMissionText("LIBC_GLOB").split("|");
		for(var i=0;i<a.length;i++) if(ok.indexOf(a[i])===-1) warn.push(a[i]);
		if(warn.length){
			log("WARNING","Warning! Global namespace polluted by:");
			t = JSON.stringify(warn);
			log("WARNING",t);
		}
	},
	_cmpNum: function(a,b){return a-b;},
	_cmpGen: function(a,b){return (b<a)-(a<b);},
	_cmpObjKey: function(a,b,k){return a[k]-b[k];},
	_cmpArrKey: function(a,b){return a[0]-b[0];},
	_getMat: function(ent){
		var ta = ent.getMaterials(),tb = Object.keys(ta);
		return ta[tb[0]];
	},
	// Collecting the data + Class A algorithm is expensive.
	_getConnected: function(gal,sys,coo){
		var gn = galaxyNumber,
			id = system.ID,
			cpp = player.ship.galaxyCoordinatesInLY,
			check = [], cur = [], gInfo = [],
			c,cp,dist,len,i,o,r,s,t,u,w;
		if(typeof gal==='number'){
			gn = gal;
			cpp = coo;
			id = sys;
		}
		// Collect coordinates
		for(i=0;i<256;i++){
			c = System.infoForSystem(gn,i).coordinates;
			r = {x: c.x, y: c.y, id: i, done: 0};
			gInfo.push(r);
		}
		cp = {x: cpp.x, y: cpp.y, z: 0};
		gInfo = this.arrSortBy(gInfo,"x");
		this.$connections[gn] = [];
		// Start loop
		for(u=0;u<256;u++){
			t = gInfo[u];
			if(t.x>cp.x+7.15) break;
			if(t.x<cp.x-7.15 || t.y<cp.y-7.15 || t.y>cp.y+7.15) continue;
			dist = Math.sqrt((cp.x-t.x)*(cp.x-t.x)+(cp.y-t.y)*(cp.y-t.y));
			if(dist<7.341) cur.push(t);
		}
		// Grow
		while(cur.length){
			check = check.concat(cur);
			cur = [];
			for(o=0;o<check.length;o++){
				cp = check[o];
				if(cp.id===id || cp.done) continue;
				for(i=0;i<256;i++){
					t = gInfo[i];
					if(t.x>cp.x+7.15) break;
					if(t.x<cp.x-7.15 || t.y<cp.y-7.15 || t.y>cp.y+7.15) continue;
					dist = Math.sqrt((cp.x-t.x)*(cp.x-t.x)+(cp.y-t.y)*(cp.y-t.y));
					if(dist<7.341 && check.indexOf(t)===-1 && cur.indexOf(t)===-1) cur.push(t);
				}
			}
			len = check.length;
			for(w=0;w<len;w++) check[w].done = 1;
		}
		len = check.length;
		for(s=0;s<len;s++) this.$connections[gn].push(check[s].id);
	},
	_objHasOwn: function(obj,key){return Object.prototype.hasOwnProperty.call(obj,key);},
	_objRet: function(obj,prop){
		if(obj[prop] && typeof obj[prop]==='object') return obj[prop];
		return obj;
	},
	_ooGame: function(){return oolite.gameSettings;},
	_sanity: function(){return {changed:this.$entCstChanged,removed:this.$entCstRemoved,strength:this.$entLastStrength,patched:this.$entCstPatched};},
	_shuffle: function(){return 0.5-Math.random();}
};
/* Do before startUp. These features are useful for other OXPs, but are somewhat slow
	and it's better to have it done only once instead of slow code in lots of OXPs.
*/
this._lib = new this._libMain(); // Create instance
this._lib.$ships = Ship.keys().sort();

this.startUp = this.playerEnteredNewGalaxy = function(){
	this._lib._getConnected();
};
this.missionScreenOpportunity = function(){
	delete this.missionScreenOpportunity;
	this._lib._chkGlobal();
	this.shipWillExitWitchspace();
};
this.shipWillExitWitchspace = function(){
	system.addShips('lib_test',1,[99999999,99999999,99999999],15000);
};

}).call(this);
Scripts/Lib_MissionCoord.js
/* jshint bitwise:false, forin:false */
/* global galaxyNumber,log,system,worldScripts */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_MissionCoord";

// TODO: use $missionActive in Lib_Main
this.$pool = [];
this.startUp = function(){
	delete this.startUp;
	this._bSearch = new worldScripts.Lib_BinSearch._lib_BinSearch();
	this.$checked = false;
	this.$isMission = false;
	this.$dbg = false;
};
this._prepare = function(){
	var s = [],r = this.$pool.length;
	if(r){
		var merged = [],pos=0;
		for(var m=0;m<r;m++){
			s = this.$pool[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 j=0;j<r;j+=2){
			this._bSearch.add(merged[j],merged[j+1]);
		}
		if(this.$dbg) 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;
};
this._performCheck = function(early){
	this.$checked = true;
	if(this.$isMission) return;
	var test = system.description,prop;
	var pRand = Math.floor(100*system.scrambledPseudoRandomNumber(system.ID));
	var rest = test.replace(/[^a-zA-Z]/g,' ');
	rest = rest.split(" ");
	rest = worldScripts.Lib_Main._lib.arrUnique(rest);
	var fflag = false, l = rest.length, muteGroup = [];
	for(var s=0;s<l;s++){
		fflag = this._bSearch.contains(rest[s]);
		if(fflag){
			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.$dbg) 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.$isMission = true;
					}
				}
			}
		}
	}
};
this.alertConditionChanged = this.guiScreenChanged = function(){
	if(this._prepare) this._prepare();
	if(!this.$checked && this._performCheck) this._performCheck();
};
this.shipWillExitWitchspace = function(){
	this.$isMission = 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();
};
}).call(this);
Scripts/Lib_Music.js
/* jshint forin:false, bitwise:false */
/* global Sound,SoundSource,Timer,clock,guiScreen,mission,missionVariables,player,system,worldScripts */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_Music";
this.copyright = "(C)2016-2018";
this.description = "Event-driven and generic music handling.";

this.$E0 = 0;
this.$inf = {
	Name:"Lib_Music",Display:"Music-Config",Alive:"$inf",Alias:"Library",
	SInt:{S0:{Name:"$dub.vol",Def:0.8,Min:0,Max:1,Float:true,Desc:"Volume",Notify:"_updateVol"}},
	EInt:{E0:{Name:"$E0",Def:0,Min:0,Max:1,Hide:true,Desc:[" "]},Info:"No AddOn installed.",Notify:"_buildList"}
};
this.$act = {
	generic:0
};
this.$groupIDs = [];
this.$radios = [];
this.$media = {
	aegis:{enter:[],exit:[]},
	alert:{red:[]},
	docked:{main:[],any:[]},
	exitWS:{inter:[],goneNova:[],doNova:[],standard:[]},
	killed:{any:[]},
	launch:{inter:[],goneNova:[],doNova:[],main:[],any:[]},
	planetIn:{enterMain:[],enterSun:[],any:[]},
	planetOut:{exitMain:[],exitSun:[],any:[]},
	radio:{generic:[]},
	rescued:{any:[]},
	scooped:{any:[]},
	scoopFuel:{fuel:[]}
};
this.$dub = {
	auto:0,
	channel:null,
	cnt:0,
	cur:null,
	dur:0,
	ent:null,
	hand:null,
	kick:0,
	lockAlert:1,
	lockDock:0,
	lockFuel:1,
	lockKill:1,
	lockPlanet:1,
	now:0,
	queue:[],
	radio:null,
	reinit:0,
	spec:null,
	vol:0.8
};
this.startUp = function(){
	delete this.startUp;
	var mus;
	this._aid = worldScripts.Lib_Main._lib;
	this.$snd = new SoundSource();
	this.$snd.sound = "lib_music_fade.ogg";
	if(missionVariables.LIB_MUSIC){
		mus = JSON.parse(missionVariables.LIB_MUSIC);
		this.$dub.vol = mus.vol;
		this.$snd.volume = mus.vol;
	}
};
this.playerWillSaveGame = function(){
	var t, s = [], ind, mus = {};
	if(this.$E0){
		t = [].concat(this.$inf.EInt.E0.Desc);
		for(var i=0;i<t.length;i++){
			ind = Math.pow(2,i);
			if(this.$E0&ind) s.push(t[i]);
		}
		if(s.length>24) s.length = 24;
		missionVariables.LIB_RADIO = JSON.stringify(s);
	} else missionVariables.LIB_RADIO = null;
	mus.vol = this.$dub.vol;
	missionVariables.LIB_MUSIC = JSON.stringify(mus);
};
/** Expects Object with the structure of this.$media (can be partial),
* except radio. The Arrays contain Objects with members:
* {snd: <filename>, dur: duration seconds <integer>, vol: optional, volume 0...1 <float>}
*/
this._addEventMusic = function(obj,ID){
	if(!obj || typeof obj!=="object" || typeof ID!=="string") return false;
	var c = this._aid.objClone(obj);
	for(var h in c){
		if(!this.$media[h] || h==="radio") continue;
		for(var s in c[h]){
			if(s==="any" && !this.$media[h][s]) continue;
			if(!this.$media[h][s]) this.$media[h][s] = [];
			for(var i=0;i<c[h][s].length;i++){
				c[h][s][i].act = ID;
				this.$media[h][s].push(c[h][s][i]);
			}
		}
	}
	if(this.$actClone){
		this.$actClone[ID] = 0;
		this.$act[ID] = 0;
	} else if(typeof this.$act[ID]!=="number") this.$act[ID] = 0;
	if(this.$groupIDs.indexOf(ID)===-1) this.$groupIDs.push(ID);
	return true;
};
/** Toggle status for group ID.
* - ID: Name <string>
*/
this._toggleGroup = function(ID){
	var a = this.$act;
	if(typeof a[ID]!=="number") return false;
	if(this.$actClone) a = this.$actClone;
	if(!a[ID]) a[ID] = 1;
	else a[ID] = 0;
	return a[ID];
};
/** Take priority for the specified GroupID.
* - ID: Name <string>
*/
this._setPriorityGroup = function(ID){
	var g,i,l;
	if(typeof this.$act[ID]!=="number") return false;
	this._clrPriorityGroup();
	this.$actClone = this._aid.objClone(this.$act);
	g = this.$groupIDs;
	l = g.length;
	for(i=0;i<l;i++) this.$act[g[i]] = 0;
	this.$act[ID] = 1;
	return true;
};
/** Clear priority.
*/
this._clrPriorityGroup = function(){
	if(this.$actClone) this.$act = this.$actClone;
	delete this.$actClone;
	return;
};
/** Add radio channel or merges. Expects Object with members:
* - name: Channel name <string>. Can be generic, docking, fight, (docked) entity name or a custom channel
* - sounds: Array of Objects with {snd: <filename>, dur: duration seconds <integer>, vol: optional, volume <float>}
* - radio: If set channel name will be listed in this.$radios
*/
this._addChannel = function(obj){
	var c = this._aid.objClone(obj),i,len=obj.sounds.length;
	if(!this.$media.radio[c.name]) this.$media.radio[c.name] = [];
	for(i=0;i<len;i++){
		c.sounds[i].act = c.name;
		this.$media.radio[c.name].push(c.sounds[i]);
	}
	if(this.$actClone){
		this.$actClone[c.name] = 1;
		this.$act[c.name] = 0;
	} else this.$act[c.name] = 1;
	if(c.radio && this.$radios.indexOf(c.name)===-1) this.$radios.push(c.name);
	return true;
};
/** Sets or clears custom radio channel.
* - str: Channel name <string> or null.
*/
this._setChannel = function(str){
	var d = this.$dub;
	switch(typeof str){
		case "string": if(typeof this.$act[str]==="number" && typeof this.$media.radio[str]!=="undefined") d.channel = str; break;
		case "object": if(!str) d.channel = null; break;
	}
	return d.channel;
};
this._buildList = function(){
	var c = this.$radios, l = c.length, ind, d = [];
	for(var i=0;i<l;i++){
		ind = Math.pow(2,i);
		if(this.$E0&ind) d = d.concat(this.$media.radio[c[i]]);
	}
	if(this.$E0 && d.length){
		this._addChannel({name:"RadioPlaylist",sounds:[]});
		this.$media.radio.RadioPlaylist = d;
		this._setChannel("RadioPlaylist");
	} else this.$dub.channel = null;
};
this._updateInf = function(){
	var c = this.$radios, l = c.length, max = Math.pow(2,l)-1, o = this.$inf.EInt;
	o.E0.Max = max&0xffffff;
	o.E0.Desc = [].concat(c);
	if(l){
		o.E0.Hide = false;
		o.Info = "Push selected radio channels to main playlist.";
	}
};
this._updateVol = function(){
	var d = this.$dub, mus, vol, cvol=1, i;
	if(d.cur){
		mus = Sound.musicSoundSource();
		vol = d.vol;
		if(mus.isPlaying){
			for(i=0;i<d.queue.length;i++){
				if(d.queue[i].snd===d.cur){
					if(d.queue[i].vol) cvol = d.queue[i].vol;
					break;
				}
			}
			mus.volume = vol*cvol;
		}
	}
	this.$snd.volume = d.vol;
};
this._selectGeneric = function(d){
	if(d.channel){
		d.queue = this.$media.radio[d.channel];
		d.spec = "generic";
	} else {
		if(d.radio){
			d.queue = this.$media.radio[d.radio];
			d.spec = d.radio;
		} else {
			d.queue = this.$media.radio.generic;
			d.spec = "generic";
		}
	}
	d.hand = "radio";
	d.now = 0;
};
this._resetTimer = function(t){
	if(this.$mediaTimer) this.$mediaTimer.stop();
	this.$mediaTimer = null;
	if(typeof t==="number") this.$mediaTimer = new Timer(this,this._doPlay,t,1);
};
this._doPlay = function(q,d){
	var sel,r,found,vol;
	if(!d) d = this.$dub;
	d.cnt++;
	if(!q){
		q = [];
		if(d.lockDock && player.ship.isValid && player.ship.docked){
			d.lockDock = 0;
			this.shipDockedWithStation(player.ship.dockedStation);
			return;
		}
	}
	if(d.cnt>d.dur){
		if(!d.now && d.hand==="radio") this._selectGeneric(d);
		if(!q.length){
			this._selectGeneric(d);
			q = d.queue;
			if(q.length) found = 1;
		} else found = 1;
		if(found){
			if(d.now && d.kick) this.$snd.play();
			// TODO: selection
			r = Math.floor(Math.random()*q.length);
			sel = q[r];
			vol = d.vol;
			if(sel.vol) vol *= sel.vol;
			Sound.playMusic(sel.snd,false,vol);
			d.dur = sel.dur;
			d.cnt = d.dur-2;
			d.cur = sel.snd;
		} else {
			d.dur = 1;
			d.cnt = 0;
		}
		d.ent = null;
		d.now = 0;
		d.kick = 0;
		if(found) this._resetTimer(d.dur+this._aid.randXY(13,22));
		else this._resetTimer();
		if(d.hand!=="radio") return d.dur;
		else return 10;
	} else d.hand = "radio";
	return 0;
};
this._performMedia = function(hand,spec,ent){
	var d = this.$dub,i,q=[],len,w, add = this.$media[hand].any, any = 1;
	if(hand!=="alert" && guiScreen==="GUI_SCREEN_MISSION") return 0;
	if(!ent && d.radio==="fight" && player.alertHostiles && hand!=="killed") return 0;
	if(ent && this.$media[hand][ent]){
		w = this.$media[hand][ent];
		if(w.length) any = 0;
	} else w = this.$media[hand][spec];
	if((!w || !w.length) && any && add){
		if(w) w = w.concat(add);
		else w = add;
	}
	if(w){
		len = w.length;
		// TODO: move the hard work to _toggleGroup and _setPriorityGroup
		for(i=0;i<len;i++){
			if(this.$act[w[i].act]) q.push(w[i]);
		}
		if(q.length){
			d.cnt = 1;
			d.dur = 0;
			d.hand = hand;
			d.spec = spec;
			d.queue = q;
			if(ent) d.ent = ent;
			else d.ent = null;
			d.now = 1;
			return this._doPlay(q,d);
		}
	}
	d.kick = 0;
	return 10;
};
// Handler
this.alertConditionChanged = function(to){
	var c = clock.absoluteSeconds, d = this.$dub;
	if(!player.ship.isInSpace) return;
	switch(to){
		case 1:
		case 2: 
			if(d.radio==="fight") d.radio = "generic";
			break;
		case 3:
			if(c>=d.lockAlert){
				d.kick = 1;
				d.lockAlert = c+this._performMedia("alert","red");
			}
			if(this.$media.radio.fight){
				d.radio = "fight";
				if(!this.$mediaTimer || !this.$media.alert.red.length) this._resetTimer(0);
			}
			break;
	}
};
this.playerRescuedEscapePod = function(fee,reason,occupant){
	var c = clock.absoluteSeconds;
	if(!occupant || !occupant.name || c<this.$dub.lockDock) return;
	this.$dub.lockDock = clock.absoluteSeconds+this._performMedia("rescued","ent",occupant.name);
};
this.shipDockedWithStation = function(station){
	var c = clock.absoluteSeconds, d = this.$dub;
	this._clrLock();
	if(!station) return;
	if(this.$media.radio[station.name]) d.radio = station.name;
	else d.radio = "generic";
	if(c<d.lockDock) return;
	this._resetTimer(0); // autopilot!
	if(station.isMainStation) this._performMedia("docked","main");
	else this._performMedia("docked","ent",station.name);
	d.lockDock = 0;
};
this.shipEnteredPlanetaryVicinity = function(planet){
	var aa,ab,c = clock.absoluteSeconds, d = this.$dub;
	if(!player.ship.isInSpace || !planet || c<d.lockPlanet) return;
	if(planet.isMainPlanet) aa = "enterMain";
	else if(planet.isSun) aa = "enterSun";
	else {
		aa = "ent";
		ab = planet.name;
	}
	d.kick = 1;
	d.lockPlanet = c+this._performMedia("planetIn",aa,ab);
};
this.shipEnteredStationAegis = function(station){
	var c = clock.absoluteSeconds, d = this.$dub;
	if(!player.ship.isInSpace || !station || c<d.lockPlanet) return;
	d.kick = 1;
	d.lockPlanet = c+this._performMedia("aegis","enter");
};
this.shipExitedStationAegis = function(station){
	var c = clock.absoluteSeconds, d = this.$dub;
	if(!player.ship.isInSpace || !station || c<d.lockPlanet) return;
	d.kick = 1;
	d.lockPlanet = c+this._performMedia("aegis","exit");
};
this.shipExitedPlanetaryVicinity = function(planet){
	var aa,ab,c = clock.absoluteSeconds, d = this.$dub;
	if(!player.ship.isInSpace || !planet || c<d.lockPlanet) return;
	if(planet.isMainPlanet) aa = "exitMain";
	else if(planet.isSun) aa = "exitSun";
	else {
		aa = "ent";
		ab = planet.name;
	}
	d.kick = 1;
	d.lockPlanet = c+this._performMedia("planetOut",aa,ab);
};
this.shipExitedWitchspace = function(){
	this._clrLock();
	var a;
	if(!player.ship.isInSpace) return;
	if(system.isInterstellarSpace) a = "inter";
	else if(system.sun.hasGoneNova) a = "goneNova";
	else if(system.sun.isGoingNova) a = "doNova";
	else a = "standard";
	this.$dub.kick = 1;
	this._performMedia("exitWS",a);
};
this.shipKilledOther = function(whom){
	var c = clock.absoluteSeconds, d = this.$dub;
	if(!player.ship.isInSpace || c<d.lockKill) return;
	d.kick = 1;
	if(whom && whom.name) d.lockKill = c+this._performMedia("killed","ent",whom.name);
	else d.lockKill = c+this._performMedia("killed","any");
};
this.shipScoopedFuel = function(){
	var c = clock.absoluteSeconds, d = this.$dub;
	if(!player.ship.isInSpace || c<d.lockFuel) return;
	d.kick = 1;
	d.lockFuel = c+this._performMedia("scoopFuel","fuel");
};
this.shipScoopedOther = function(whom){
	if(!player.ship.isInSpace || !whom || !whom.name) return;
	this.$dub.kick = 1;
	this._performMedia("scooped","ent",whom.name);
};
this.shipTargetDestroyed = function(target){
	var c = clock.absoluteSeconds, d = this.$dub, t;
	if(!player.ship.isInSpace || c<d.lockKill) return;
	d.kick = 1;
	if(target){
		t = target.name;
		if(t && this.$media.killed[t]){
			d.lockKill = c+this._performMedia("killed","ent",t);
			return;
		}
	}
	d.lockKill = c+this._performMedia("killed","any");
};
this.shipWillDockWithStation = function(){
	var d = this.$dub;
	d.auto = 0;
	if(d.cur) Sound.stopMusic(d.cur);
};
this.shipWillLaunchFromStation = function(station){
	var d = this.$dub;
	if(!station) return;
	d.radio = "generic";
	d.lockDock = 0;
	d.kick = 1;
	this._resetTimer(0);
	if(station.isMainStation) this._performMedia("launch","main");
	else this._performMedia("launch","ent",station.name);
};
// Special cases
this.gamePaused = function(){
	if(this.$dub.cur) Sound.stopMusic(this.$dub.cur);
	this._clrLock();
};
this.gameResumed = function(){
	if(this.$dub.auto && this.$media.radio.docking){
		this.playerStartedAutoPilot();
	} else this._resetTimer(0);
};
this.guiScreenChanged = function(){
	var d = this.$dub, k,m;
	if(guiScreen==="GUI_SCREEN_MISSION"){
		m = mission.screenID;
		if(m) k = worldScripts.Lib_GUI.$IDRules[m];
		if(!m || !k || !k.mus){
			if(d.cur) Sound.stopMusic(d.cur);
			this._resetTimer();
			d.reinit = 1;
		}
	}
};
this.missionScreenEnded = function(){
	if(this.$dub.reinit){
		this.$dub.reinit = 0;
		this._resetTimer(0);
	}
};
// Start session
this.missionScreenOpportunity = function(){
	delete this.missionScreenOpportunity;
	if(guiScreen!=="GUI_SCREEN_MISSION") this.shipDockedWithStation(player.ship.dockedStation);
	else this.$dub.reinit = 1;
	var c = this.$radios, l = c.length, ind, s;
	this._updateInf();
	if(missionVariables.LIB_RADIO){
		s = JSON.parse(missionVariables.LIB_RADIO);
		for(var i=0;i<l;i++){
			ind = c.indexOf(s[i]);
			if(ind!==-1) this.$E0 += Math.pow(2,ind);
		}
		this._buildList();
	}
	worldScripts.Lib_Config._registerSet(this.$inf);
};
this.playerCancelledAutoPilot = function(){
	var d = this.$dub;
	if(d.auto && d.cur) Sound.stopMusic(d.cur);
	d.radio = "generic";
	d.auto = 0;
	this._resetTimer(0);
};
this.playerStartedAutoPilot = function(){
	if(this.$media.radio.docking){
		Sound.stopMusic();
		this.$dub.radio = "docking";
		this.$dub.auto = 1;
		this._resetTimer(0);
	} else this._resetTimer();
};
this.shipDied = function(){
	this._resetTimer();
	if(this.$dub.cur) Sound.stopMusic(this.$dub.cur);
};
this.shipLaunchedEscapePod = function(){
	this.shipDied();
};
this._clrLock = function(){
	var d = this.$dub;
	d.lockAlert = 1;
	d.lockFuel = 1;
	d.lockKill = 1;
	d.lockPlanet = 1;
};
}).call(this);
Scripts/Lib_PAD.js
/* jshint bitwise:false, forin:false */
/* global clock,expandMissionText,mission,missionVariables,player,worldScripts */
/* (C) Svengali & BlackWolf 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_PAD";

this.$data = {
	categories:{GALCOP:"GalCop",GUILDS:"Guilds",INFOS:"Infos",LOGS:"Logbook",PERSONS:"Persons",SYSTEMS:"Systems",ZZX:"Search",ZZY:"Player data",ZZZ:"Exit"},
	GALCOP:{
		GENERIC:{name:"",entry:"2084004:01:01:01",enlisted:"",kills:0,missions:[],awards:[],info:[],t0:11,t1:0,t2:0,t3:0,t4:0,t5:0},
		NAVY:{name:"",entry:"",enlisted:"",kills:0,missions:[],awards:[],info:[],t0:21,t1:0,t2:0,t3:0,t4:0,t5:0}
	},
	GUILDS:{
		GENERIC:{name:"",entry:"2084004:01:01:01",enlisted:"Trade Guild Member",kills:0,missions:[],awards:[],info:[],t0:20,t1:0,t2:0,t3:0,t4:0,t5:0}
	},
	INFOS:{
		GENERIC:{name:"Lave Academy",location:"Lave",beacon:"None",purpose:"Flight Exam",special:["Exam 1st grade."],notes:[],t0:20,t1:0,t2:0,t3:0,t4:0,t5:0}
	},
	LOGS:{
		GENERIC:{list:["2084004:01:01 Starting my career."]}
	},
	PERSONS:{
		GENERIC:{name:"",origin:"",species:"",gender:"",age:0,ship:"",rank:"",info:[],notes:[],t0:0,t1:0,t2:0,t3:0,t4:0,t5:0}
	},
	SYSTEMS:{
		GENERIC:{name:"Lave G1",info:["Located in the south-west of Galaxy 1.","Part of the Old Worlds.","Seat of the Galactic Co-operative of Worlds."],notes:[],t0:0,t1:0,t2:0,t3:0,t4:0,t5:0}
	}
};
this.$config = {
	curCat:"GALCOP",
	def:{XXX:"Page up",YYY:"Page down",ZZZ:"Back"},
	defSpecies:["Bird","Feline","Frog","Human","Humanoid","Insect","Lizard","Lobster","Rodent"],
	defGender:["Male","Female","Neutral","Android"],
	defImages:[
		{t1:"lib_user.png",s:3,g:0,a:[26,27]},
		{t1:"lib_avatar01.png",s:3,g:1,a:[23,36]},{t1:"lib_avatar02.png",s:3,g:1,a:[23,32]},{t1:"lib_avatar03.png",s:3,g:1,a:[23,32]},
		{t1:"lib_avatar04.png",s:3,g:0,a:[26,37]},{t1:"lib_avatar05.png",s:3,g:0,a:[24,33]},{t1:"lib_avatar06.png",s:3,g:0,a:[44,63]},
		{t1:"lib_avatar07.png",s:8,g:1,a:[23,43]},{t1:"lib_avatar08.png",s:8,g:0,a:[30,43]},{t1:"lib_avatar09.png",s:0,g:0,a:[28,43]},
		{t1:"lib_avatar10.png",s:0,g:1,a:[28,43]},{t1:"lib_avatar11.png",s:1,g:1,a:[28,43]},{t1:"lib_avatar12.png",s:1,g:0,a:[34,48]},
		{t1:"lib_avatar13.png",s:1,g:1,a:[24,41]},{t1:"lib_avatar14.png",s:1,g:0,a:[24,41]},{t1:"lib_avatar15.png",s:2,g:2,a:[24,41]},
		{t1:"lib_avatar16.png",s:2,g:2,a:[24,41]},{t1:"lib_avatar17.png",s:5,g:2,a:[24,51]},{t1:"lib_avatar18.png",s:5,g:2,a:[24,51]},
		{t1:"lib_avatar19.png",s:6,g:0,a:[24,151]},{t1:"lib_avatar20.png",s:6,g:1,a:[24,111]},{t1:"lib_avatar21.png",s:8,g:1,a:[21,31]},
		{t1:"lib_avatar22.png",s:8,g:0,a:[24,35]}
	],
	defTemps:{
		GALCOP:{
			disp:{name:"",entry:"",enlisted:"",kills:0,missions:[],awards:[],info:[]},
			silent:{missions:5,awards:5,info:5,t0:1,t1:1,t2:1,t3:1,t4:1,t5:1,model:"lib_ms_helper6y"}
		},
		GUILDS:{
			disp:{name:"",entry:"",enlisted:"",kills:0,missions:[],awards:[],info:[]},
			silent:{missions:5,awards:5,info:5,t0:1,t1:1,t2:1,t3:1,t4:1,t5:1,model:"lib_ms_helper6y"}
		},
		INFOS:{
			disp:{name:"",location:"",beacon:"",purpose:"",special:[],notes:[],$crypt:""},
			silent:{special:10,$crypt:1,notes:5,t0:1,t1:1,t2:1,t3:1,t4:1,t5:1,model:"lib_ms_helper6y"}
		},
		LOGS:{
			disp:{list:[]},silent:{list:20,model:null}
		},
		PERSONS:{
			disp:{name:"",origin:"",species:"Human",gender:"Male",age:0,rank:"",ship:"",info:[],notes:[]},
			silent:{info:7,notes:5,t0:1,t1:1,t2:1,t3:1,t4:1,t5:1,model:"lib_ms_helper6y"}
		},
		SYSTEMS:{
			disp:{name:"",info:[],notes:[],$hide:""},
			silent:{info:10,notes:5,$hide:1,t0:1,t1:1,t2:1,t3:1,t4:1,t5:1,model:"lib_ms_helper6y"}
		}
	},
	defOrgs:[null,
		"lib_pad_org1.png","lib_pad_org2.png","lib_pad_org3.png","lib_pad_org4.png","lib_pad_org5.png","lib_pad_org6.png",
		"lib_pad_org7.png","lib_pad_org8.png","lib_pad_org9.png","lib_pad_org10.png","lib_pad_org11.png","lib_pad_org12.png",
		"lib_pad_org13.png","lib_pad_org14.png","lib_pad_org15.png","lib_pad_org16.png","lib_pad_org17.png","lib_pad_org18.png",
		"lib_pad_org19.png","lib_pad_org20.png","lib_pad_org21.png","lib_pad_org22.png","lib_pad_org23.png","lib_pad_org24.png",
		"lib_pad_org25.png","lib_pad_org26.png","lib_pad_org27.png","lib_pad_org28.png","lib_pad_org29.png","lib_pad_org30.png",
		"lib_pad_org31.png"
	],
	defRanks:[null,
		"lib_pad_rank1.png","lib_pad_rank2.png","lib_pad_rank3.png","lib_pad_rank4.png","lib_pad_rank5.png","lib_pad_rank6.png",
		"lib_pad_rank7.png","lib_pad_rank8.png","lib_pad_rank9.png"
	],
	defAwards:[null,
		"lib_pad_medal1.png","lib_pad_medal2.png","lib_pad_medal3.png","lib_pad_medal4.png","lib_pad_medal5.png","lib_pad_medal6.png",
		"lib_pad_medal7.png","lib_pad_medal8.png","lib_pad_medal9.png","lib_pad_medal10.png","lib_pad_medal11.png","lib_pad_medal12.png",
		"lib_pad_medal13.png","lib_pad_medal14.png","lib_pad_medal15.png","lib_pad_medal16.png","lib_pad_medal17.png"
	],
	defFFF:[null,"lib_pad_rank1.png"],
	defGGG:[null,"lib_pad_rank1.png"],
	defTex:["t0","t1","t2","t3","t4","t5"],
	defTexLookUp:["defOrgs","defImages","defRanks","defAwards","defFFF","defGGG"],
	defHead:{
		exitScreen:"GUI_SCREEN_INTERFACES",
		message:"",
		background:{name:"lib_pad_text_bg.png",height:512},
		screenID:this.name,
		spinModel:false
	},
	hide:["$crypt","$hide"],
	HUD:0,
	last:{
		news:"",
		add:"",
		upd:""
	},
	lastSearch:"",
	noteAdd:"",
	page:"INIT",
	pageInd:0,
	setInd:0,
	setGal:0,
	relInd:0,
	ps:{
		age:26,
		gender:"Male",
		species:"Human",
		origin:"Lave",
		image:"lib_user.png"
	},
	psUpd:1
};
this.startUp = function(){
	var d = this.$data,
		load;
	this._aid = worldScripts.Lib_Main._lib;
	worldScripts.Lib_GUI.$IDRules.Lib_PAD = {mus:1};
	if(missionVariables.LIB_PAD_DATA){
		load = JSON.parse(missionVariables.LIB_PAD_DATA);
		d.GALCOP.GENERIC = load.GALCOP;
		d.GALCOP.NAVY = load.NAVY;
		d.GUILDS.GENERIC = load.GUILDS;
		d.INFOS.GENERIC = load.INFOS;
		d.LOGS.GENERIC = load.LOGS;
		d.PERSONS.GENERIC = load.PERSONS;
		d.SYSTEMS.GENERIC = load.SYSTEMS;
		this.$config.ps = JSON.parse(missionVariables.LIB_PAD_PS);
		this.$config.last = JSON.parse(missionVariables.LIB_PAD_LAST);
	}
	this._updatePlayer(1);
	this.$Search = {
		"GALCOP.GENERIC": [0,[],[]],
		"GALCOP.NAVY": [0,[],[]],
		"GUILDS.GENERIC": [0,[],[]],
		"INFOS.GENERIC": [0,[],[]],
		"LOGS.GENERIC": [0,[],[]],
		"PERSONS.GENERIC": [0,[],[]],
		"SYSTEMS.GENERIC": [0,[],[]]
	};
	this._fillSearch();
};
this._fillSearch = function(){
	var d = this.$data;
	this.$Search["GALCOP.GENERIC"][0] = this._getEntries(d.GALCOP.GENERIC);
	this.$Search["GALCOP.NAVY"][0] = this._getEntries(d.GALCOP.NAVY);
	this.$Search["GUILDS.GENERIC"][0] = this._getEntries(d.GUILDS.GENERIC);
	this.$Search["INFOS.GENERIC"][0] = this._getEntries(d.INFOS.GENERIC);
	this.$Search["LOGS.GENERIC"][0] = this._getEntries(d.LOGS.GENERIC);
	this.$Search["PERSONS.GENERIC"][0] = this._getEntries(d.PERSONS.GENERIC);
	this.$Search["SYSTEMS.GENERIC"][0] = this._getEntries(d.SYSTEMS.GENERIC);
};
this.startUpComplete = function(){
	this._addInterface();
};
// store only GENERIC, NAVY, last and player data
this.playerWillSaveGame = function(){
	var d = this.$data,
		store = {
			GALCOP: d.GALCOP.GENERIC,
			NAVY: d.GALCOP.NAVY,
			GUILDS: d.GUILDS.GENERIC,
			INFOS: d.INFOS.GENERIC,
			LOGS: d.LOGS.GENERIC,
			PERSONS: d.PERSONS.GENERIC,
			SYSTEMS: d.SYSTEMS.GENERIC
		};
	missionVariables.LIB_PAD_DATA = JSON.stringify(store);
	missionVariables.LIB_PAD_PS = JSON.stringify(this.$config.ps);
	missionVariables.LIB_PAD_LAST = JSON.stringify(this.$config.last);
};
this.shipDockedWithStation = function(){
	if(!player.ship.docked) return;
	this._addInterface();
};
this._limit = function(who,how){
	for(var k in who) if(typeof(who[k])==="object" && who[k].length) who[k] = who[k].splice(-how[k]);
	return who;
};
this._getEntries = function(obj){
	var ownProps = Object.keys(obj), i = ownProps.length, o, res = "", ex = ["t0","t1","t2","t3","t4","t5"];
	while(i--){
		if(ex.indexOf(ownProps[i])!==-1) continue;
		o = obj[ownProps[i]];
		if(typeof(o)==="object") res += this._getEntries(o);
		else res += "|"+o;
	}
	return res;
};
/** function _addPageInCategory
* path    String. Dotted path, e.g. "GALCOP.NAVY".
* content Object.
* parent  Array. Member of other pages.
* sil     Bool. Silent mode. Does not add to latest additions.
* $return true on success, otherwise false.
*/
this._addPageInCategory = function(path,content,parent,sil){
	var con = this.$config,
		d = this.$data,
		q = this._aid.objGet(d,path),
		s = path.split("."),
		obj = this._aid.objClone(content),
		temp = this._aid.objClone(con.defTemps[s[0]].disp),
		i;
	if(q || !temp) return false;
	for(i=0;i<6;i++) temp[con.defTex[i]] = 0;
	temp = this._aid.objMerge(temp,obj,1);
	temp = this._limit(temp,con.defTemps[s[0]].silent);
	this._aid.objSet(d,path,temp);
	if(!sil) con.last.add = s.join(":");
	con.psUpd = 1;
	this.$Search[path] = [this._getEntries(temp),[],[]];
	if(parent){
		this.$Search[path][1] = parent;
		for(i=0;i<parent.length;i++){
			this.$Search[parent[i]][2].push(path);
		}
	}
	return true;
};
/** _setPageEntry
* path   String. Dotted path, e.g. "LOGS.GENERIC.list".
* value  Primitive. Value.
* sil    Bool. Silent mode. Does not add to latest additions.
* $return true on success, otherwise false.
*/
this._setPageEntry = function(path,value,sil){
	var d = this.$data,
		q = this._aid.objGet(d,path),
		s = path.split("."),
		o = {},
		n = clock.clockString.substr(0,13),
		p;
	if(typeof(q)==="undefined") return false;
	switch(this._aid.typeGet(q)){
		case "array":
			if(s[2]==="list") q.push(n+" "+value);
			else if(q.indexOf(value)<0) q.push(value);
			o[s[2]] = q;
			q = this._limit(o,this.$config.defTemps[s[0]].silent)[s[2]];
			break;
		case "number":
			if(value==="++") q++;
			else q = value;
			break;
		default: q = value;
	}
	this._aid.objSet(d,path,q);
	p = this._aid.objGet(d,s[0]+"."+s[1]);
	if(!sil) this.$config.last.upd = s[0]+":"+s[1]+" - "+n+" "+value;
	this.$Search[s[0]+"."+s[1]][0] = this._getEntries(p);
	return true;
};
/** _getData
* path String. Dotted path, e.g. "GALCOP.NAVY".
* $return Object or null.
*/
this._getData = function(path){
	var q = this._aid.objGet(this.$data,path);
	if(q) return this._aid.objClone(q);
	return null;
};
this._addInterface = function(){
	player.ship.dockedStation.setInterface(this.name,{
		title: "P.A.D.",
		category: "Captains Log",
		summary: "Personal Assistance Device",
		callback: this._showStart.bind(this)
	});
};
this._updatePlayer = function(ex){
	var a,b,c,i, ps = this.$config.ps;
	a = this.$data.PERSONS.GENERIC;
	a.name = player.name;
	a.ship = player.ship.displayName;
	a.species = ps.species;
	a.origin = ps.origin;
	a.gender = ps.gender;
	a.age = ps.age;
	a.rank = player.rank;
	a.t1 = ps.image;
	b = this.$data.GALCOP;
	c = Object.keys(b);
	for(i=0;i<c.length;i++){
		b[c[i]].name = player.name;
		b[c[i]].t1 = ps.image;
	}
	b.GENERIC.kills = player.score;
	b = this.$data.GUILDS;
	c = Object.keys(b);
	for(i=0;i<c.length;i++){
		b[c[i]].name = player.name;
		b[c[i]].t1 = ps.image;
	}
	b.GENERIC.kills = player.score;
	this.$config.psUpd = 0;
	if(!ex) this._fillSearch();
};
this._showStart = function(ini){
	var c = this.$config;
	if(ini==="Lib_PAD"){
		c.psUpd = 1;
		if(!player.ship.hudHidden) c.HUD = 1;
	}
	player.ship.hudHidden = true;
	if(c.psUpd) this._updatePlayer();
	c.curCat = "INIT";
	var head = this._aid.objClone(c.defHead);
	head.choices = this.$data.categories;
	head.message = "Last News:\n"+c.last.news+"\n\nLast Update:\n"+c.last.upd+"\n\nLast Addition:\n"+c.last.add;
	head.background = {name:"lib_pad_bg.png",height:512};
	head.title = "Personal Assistance Device";
	mission.runScreen(head,this._choices);
};
this._showCategory = function(ick,note){
	var con = this.$config,
		cat = con.curCat,
		d = this.$data[cat],
		ind = con.pageInd,
		max = Object.keys(d),
		p = d[con.page],
		temps = Object.keys(con.defTemps),
		i,md,m,min,template,
		tt = con.defTex,
		head = this._aid.objClone(con.defHead),
		pm = this.$Search[cat+"."+con.page];
	con.relInd = 0;
	head.choices = this._aid.objClone(con.def);
	head.initialChoicesKey = ick;
	head.model = con.defTemps[cat].silent.model;
	head.title = cat+(ind!==0?"-"+max[ind]:"")+" ("+(ind+1)+"/"+max.length+")";
	if(max.length<2){
		head.choices = this._aid.scrChcUnsel(head.choices,'XXX');
		head.choices = this._aid.scrChcUnsel(head.choices,'YYY');
	}
	if(p.list){
		if(!p.list.length) head.message = "No entries yet.";
		else {
			min = p.list.length-1;
			max = Math.max(0,min-20);
			for(i=min;i>=max;i--) head.message += this._aid.scrAddLine([[p.list[i],30]],"","\n");
		}
	} else {
		if(temps.indexOf(cat)>-1){
			template = con.defTemps[cat];
			m = this._fillTemplate(p,template);
			head.message = m[0];
		} else {
			head.message = "No entries yet.";
		}
		if(m[1].silent.notes){
			if(con.page==="GENERIC") head.choices.ZZW = "Add note";
			else head.choices.ZZW = {text:"Add note",unselectable:true};
		}
		if(m[1].$crypt) head.choices.ZZV = "Decrypt";
	}
	if(pm[1].length || pm[2].length) head.choices.ZZU = "Related info";
	if(note) head.message += note;
	mission.runScreen(head,this._choices);
	md = mission.displayModel;
	if(md){
		md.orientation = [1,0,1,0]; // invisible
		md.position = [50,8,150];
		for(i=0;i<tt.length;i++){
			if(m[1][tt[i]]){
				md.subEntities[i].setMaterials({"lib_null.png":{emission_map:m[1][tt[i]]}});
				md.subEntities[i].orientation = [-1,0,1,0];
			}
		}
		md.subEntities[0].position = [100,60,160]; // organisation
		md.subEntities[2].position = [25,14,-10]; // rank/enlisted
		md.subEntities[3].position = [25,-7,-10]; // medal
		md.subEntities[4].position = [25,-28,-10]; // tex
		md.subEntities[5].position = [140,75,-80]; // tex
	}
};
this._fillTemplate = function(obj,temp,ps){
	var con = this.$config,
		a = this._aid.objClone(obj),
		b = this._aid.objClone(temp),
		ca = Object.keys(b.disp),
		m = "",i,j,min,max,fill,spec,
		tt = con.defTex,
		tts = con.defTexLookUp;
	for(i=0;i<ca.length;i++){
		if(con.hide.indexOf(ca[i])!==-1) continue;
		if(a[ca[i]]){
			if(this._aid.typeGet(a[ca[i]])==="array"){
				if(ps) continue; // opt out for gallery
				fill = b.silent[ca[i]]-1;
				if(a[ca[i]].length){
					min = a[ca[i]].length-1;
					max = Math.max(0,a[ca[i]].length-b.silent[ca[i]]);
					for(j=min;j>=max;j--){
						if(j===min) m += this._aid.scrAddLine([[ca[i].toUpperCase()+": ",7],[a[ca[i]][j],23]],"","\n");
						else m += this._aid.scrAddLine([[" ",7],[a[ca[i]][j],23]]," ","\n");
					}
					if(fill>min){ // Fill up
						fill -= min;
						while(fill--) m += "\n";
					}
				} else { // Fill up
					for(j=fill;j>=0;j--){
						if(j===fill) m += this._aid.scrAddLine([[ca[i].toUpperCase()+": ",7],["-",23]],"","\n");
						else m += "\n";
					}
				}
			} else m += this._aid.scrAddLine([[ca[i].toUpperCase()+": ",7],[a[ca[i]],23]],"","\n");
		} else m += this._aid.scrAddLine([[ca[i].toUpperCase()+": ",7],["-",23]],"","\n");
	}
	for(i=0;i<tt.length;i++){
		spec = a[tt[i]];
		if(spec && b.silent[tt[i]]){
			if(typeof(spec)==="number"){
				if(spec>0 && spec<con[tts[i]].length) b[tt[i]] = con[tts[i]][spec];
			} else b[tt[i]] = spec;
		}
	}
	if(a.$crypt && b.silent.$crypt) b.$crypt = a.$crypt;
	return [m,b];
};
this._showPlayer = function(){
	this._updatePlayer();
	var arr = ["LIB_PAD_AVATAR","LIB_PAD_SPECIES","LIB_PAD_ORIGIN","LIB_PAD_GENDER","LIB_PAD_AGE"],
		c = this.$config,m,md,mdt,min,i,
		head = this._aid.objClone(c.defHead);
	head.textEntry = true;
	head.model = "lib_ms_helper";
	head.title = "Personal Assistance Device";
	if(c.setInd===0) head.model = "lib_ms_helper12x"; // Gallery
	m = this._fillTemplate(this.$data.PERSONS.GENERIC,c.defTemps.PERSONS,1);
	head.message = m[0];
	head.message += "\n"+expandMissionText(arr[c.setInd]);
	mission.runScreen(head,this._pSettings);
	md = mission.displayModel;
	if(md && m && m[1].t1){
		md.setMaterials({"lib_null.png":{emission_map:m[1].t1}});
		md.orientation = [1,0,0,0];
		if(this.$config.setInd===0){
			mdt = c.defImages;
			min = Math.min(mdt.length,c.setGal+12);
			for(i=c.setGal;i<min;i++) md.subEntities[i-c.setGal].setMaterials({"lib_null.png":{diffuse_map:"lib_pad"+(i-c.setGal+1)+".png",emission_map:mdt[i].t1}});
			md.position = [0,-12,150];
		} else md.position = [50,40,150];
	}
};
this._pSettings = function(choice){
	var c = this.$config, cint = parseInt(choice), mdt, v;
	if(choice){
		switch(c.setInd){
			case 0:
				if(/\.png/.test(choice)){ // No way to test if image exists. Undocumented.
					c.ps.image = choice;
				} else {
					if(!isNaN(cint)){
						mdt = c.defImages;
						if(cint===0){ // Next gallery
							c.setInd--;
							c.setGal += 12;
							if(c.setGal>mdt.length-1) c.setGal = 0;
						} else {
							if(cint<=mdt.length && cint>0){
								v = mdt[c.setGal+cint-1];
								if(!v) v = {t1:"lib_user.png",s:3,g:0,a:[26,27]};
								c.ps.image = v.t1;
								c.ps.species = c.defSpecies[v.s];
								c.ps.gender = c.defGender[v.g];
								c.ps.age = this._aid.randXY(v.a[0],v.a[1]);
							} else c.setInd--;
						}
					} else c.setInd--;
				}
				break;
			case 1: c.ps.species = choice; break;
			case 2: c.ps.origin = choice; break;
			case 3: c.ps.gender = choice; break;
			case 4: if(!isNaN(cint) && cint>0 && cint<300) c.ps.age = cint; break;
		}
	}
	c.setInd++;
	if(c.setInd<5) this._showPlayer();
	else {
		c.curCat = "INIT";
		c.page = "INIT";
		c.pageInd = 0;
		c.psUpd = 1;
		this._showStart(1);
	}
};
this._choices = function(choice){
	var con = this.$config,
		c = Object.keys(this.$data.categories),
		cat = con.curCat,
		d = this.$data[cat],
		ind = con.pageInd,
		max,p1,p2,act,p3,p4;
	if(!cat || cat==="INIT"){
		switch(choice){
			case "ZZZ": // Bye
				if(con.HUD) player.ship.hudHidden = false;
				con.HUD = 0;
				con.curCat = "";
				con.page = "INIT";
				con.pageInd = 0;
				break;
			case "ZZY": // Player settings
				con.setInd = 0;
				this._showPlayer();
				break;
			case "ZZX": // Search
				this._showSearch();
				break;
			default:
				ind = c.indexOf(choice);
				if(ind>-1){
					con.curCat = choice;
					con.page = "GENERIC";
					con.pageInd = 0;
					this._showCategory(choice);
				}
		}
	} else {
		max = Object.keys(d);
		switch(choice){
			case "XXX": // Page up
				ind++;
				if(ind>=max.length) ind = 0;
				con.page = max[ind];
				con.pageInd = ind;
				this._showCategory(choice);
				break;
			case "YYY": // Page down
				ind--;
				if(ind<0) ind = max.length-1;
				con.page = max[ind];
				con.pageInd = ind;
				this._showCategory(choice);
				break;
			case "ZZU": // Parent/Members
				this._showRelated();
				break;
			case "ZZV": // De-Crypt
				p1 = d[con.page].$crypt.split(".");
				p2 = p1.shift();
				act = this._aid.objGet(worldScripts[p2],p1.join("."),1);
				if(act){
					p3 = d[con.page].special.join("|");
					p4 = worldScripts.Lib_Crypt._rot513(p3);
					d[con.page].special = p4.split("|");
					d[con.page].$crypt = 0;
					this.$Search[con.curCat+"."+con.page][0] = this._getEntries(d[con.page]);
					this._showCategory(choice);
				} else this._showCategory(choice,"Could not decrypt.");
				break;
			case "ZZW": // Add note
				this._showAddNote(cat+"."+con.page);
				break;
			case "ZZZ": // Back
				con.curCat = "INIT";
				con.page = "INIT";
				con.pageInd = 0;
				this._showStart(1);
				break;
		}
	}
};
this._showAddNote = function(path){
	var m = this._aid.objGet(this.$data,path+".notes"),
		f = m.slice(0).reverse(),
		head = this._aid.objClone(this.$config.defHead);
	head.textEntry = true;
	head.title = "Add note ("+path+")";
	this.$config.noteAdd = path;
	for(var i=0;i<10;i++){
		if(i<f.length) head.message += this._aid.scrAddLine([[f[i],23]],"","\n");
		else head.message += "\n";
	}
	mission.runScreen(head,this._noteAdded);
};
this._noteAdded = function(choice){
	if(choice && choice!==""){
		this._setPageEntry(this.$config.noteAdd+".notes",choice);
		this._showCategory("ZZW","Note added.");
	} else this._showCategory("ZZW");
	this.$config.noteAdd = "";
};
this._showSearch = function(){
	var c = this.$config,
		head = this._aid.objClone(c.defHead);
	if(c.psUpd) this._updatePlayer();
	head.textEntry = true;
	head.message = null;
	head.messageKey = "LIB_PAD_SEARCH";
	head.title = "Personal Assistance Device";
	mission.runScreen(head,this._pSearch);
};
this._pSearch = function(choice){
	var res = null,i,max,
		head = this._aid.objClone(this.$config.defHead);
	head.choices = {ZZZ:"Back"};
	head.title = "Search Results"+(choice?" for "+choice:"");
	if(choice){
		res = this._searchSData(choice);
		this.$config.lastSearch = choice;
	}
	if(res && res.length){
		max = Math.min(res.length,20);
		for(i=0;i<max;i++) head.choices[res[i]] = res[i].split(".").join(" : ");
		for(i=0;i<26-max;i++) head.choices["ZZY"+i] = {text:"",unselectable:true};
	} else head.message = "Nothing found.";
	mission.runScreen(head,this._pSearchRes);
};
this._pSearchRes = function(path){
	var con = this.$config,
		d = this.$data,
		k,ind;
	switch(path){
		case "ZZZ": this._showStart(1); break;
		default:
			path = path.split(".");
			con.curCat = path[0];
			con.page = path[1];
			k = Object.keys(d[path[0]]);
			ind = k.indexOf(path[1]);
			con.pageInd = ind;
			this._showCategory(path[1]);
	}
};
// Returns array with pathes
this._searchSData = function(word){
	var w = new RegExp(word),
		s = this.$Search,
		k = Object.keys(s),
		kl = k.length,
		d,
		res = [];
	for(var i=0;i<kl;i++){
		d = s[k[i]][0];
		if(w.test(d)) res.push(k[i]);
		if(res.length>20) break;
	}
	return res;
};
this._showRelated = function(){
	var con = this.$config,
		head = this._aid.objClone(con.defHead),
		pm = this.$Search[con.curCat+"."+con.page],
		pma = pm[1],
		pmb = pm[2],
		i;
	head.choices = this._aid.objClone(con.def);
	head.title = "Related to "+con.curCat+"."+con.page;
	head.overlay = {name:"lib_pad_text_ovcatmem.png",height:512};
	for(i=5*con.relInd;i<5+(5*con.relInd);i++){
		if(pma.length>i) head.choices["A"+pma[i]] = pma[i];
		else head.choices["BZZZ"+i] = {text:"",unselectable:true};
	}
	head.choices.CZZZ = {text:"",unselectable:true};
	for(i=15*con.relInd;i<15+(15*con.relInd);i++){
		if(pmb.length>i) head.choices["D"+pmb[i]] = pmb[i];
		else head.choices["EZZZ"+i] = {text:"",unselectable:true};
	}
	head.choices.FZZZ = {text:"",unselectable:true};
	head.choices.GZZZ = {text:"",unselectable:true};
	head.choices.HZZZ = {text:"",unselectable:true};
	if(pma.length<6 && pmb.length<16){
		head.choices = this._aid.scrChcUnsel(head.choices,'XXX');
		head.choices = this._aid.scrChcUnsel(head.choices,'YYY');
	}
	mission.runScreen(head,this._related);
};
this._related = function(path){
	var con = this.$config,
		pm = this.$Search[con.curCat+"."+con.page],
		pma = pm[1],
		pmb = pm[2],
		d = this.$data,
		k,ind;
	switch(path){
		case "XXX":
			con.relInd++;
			if(pma.length<=con.relInd*5 && pmb.length<=con.relInd*15) con.relInd = 0;
			this._showRelated();
			break;
		case "YYY":
			con.relInd--;
			if(con.relInd<0) con.relInd = Math.max(Math.ceil(pmb.length/15)-1,Math.ceil(pma.length/5)-1);
			this._showRelated();
			break;
		case "ZZZ": this._showCategory(); break;
		default:
			path = path.substr(1);
			path = path.split(".");
			con.curCat = path[0];
			con.page = path[1];
			k = Object.keys(d[path[0]]);
			ind = k.indexOf(path[1]);
			con.pageInd = ind;
			this._showCategory(path[1]);
	}
};
this._Help = function(what){
	var h;
	switch(what){
		case "_addPageInCategory": h = "LIB_PAD_HELP_addPageInCategory"; break;
		case "_setPageEntry": h = "LIB_PAD_HELP_setPageEntry"; break;
		case "_getData": h = "LIB_PAD_HELP_getData"; break;
		default: h = "LIB_PAD_HELP";
	}
	return expandMissionText(h);
};
}).call(this);
Scripts/Lib_PAD_Events.js
/* jshint bitwise:false, forin:false */
/* global clock,galaxyNumber,guiScreen,mission,missionVariables,player,system,worldScripts */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_PAD_Events";

this.$config = {
	bounty:0,
	credits:0,
	fined:0,
	rank:"Harmless",
	status:"Clean",
	updCR:0,
	updNavy:1,
	updNova:1,
	updTP:0
};
this.$delay = [];
this.$init = 1;
this.startUp = function(){
	this.$init = 0;
};
this.startUpComplete = function(){
	var m = ["PRELUDE","MISSION_COMPLETE","STAGE_1","RUNNING"], mc = -1, mt = -1;
	if(missionVariables.Lib_PAD_EVENTS) this.$config = JSON.parse(missionVariables.Lib_PAD_EVENTS);
	if(missionVariables.conhunt) mc = m.indexOf(missionVariables.conhunt);
	if(missionVariables.thargplans) mt = m.indexOf(missionVariables.thargplans);
	if(this.$config.updNavy && worldScripts.Lib_PAD.$data.GALCOP.NAVY.entry==="") this._add("GALCOP.NAVY.entry",clock.clockString,1);
	this.$config.updNavy = 0;
	if(mc>-1) this._addCurruthers(1);
	if(mt>-1){
		this._addFortesque(1);
		if(mt>0) this._addBlake(1);
	}
	if(this.$config.updNova && missionVariables.nova==="NOVA_HERO"){
		this._add("PERSONS.GENERIC.info","Survived System Nova.",1);
		this.$config.updNova = 0;
	}
	this.$config.bounty = player.bounty;
	this.$config.credits = player.credits;
	this.$config.rank = player.rank;
	this.$config.status = player.legalStatus;
};
this.playerWillSaveGame = function(){
	missionVariables.Lib_PAD_EVENTS = JSON.stringify(this.$config);
};
this.guiScreenChanged = function(){
	if(!player.ship.docked) return;
	if(guiScreen==="GUI_SCREEN_MISSION" && mission.screenID){
		switch(mission.screenID){
			case "oolite-constrictor-hunt-briefing":
				if(this.$config.updNavy) this._add("GALCOP.NAVY.entry",clock.clockString.substr(0,13));
				this.$config.updNavy = 0;
				this._add("LOGS.GENERIC.list","A job for the Navy to hunt a ship.");
				this._addCurruthers();
				this.$config.updCR = 1;
				break;
			case "oolite-constrictor-hunt-debriefing":
				this._add("LOGS.GENERIC.list","Yeeehaa. Got the Constrictor!");
				this._add("GALCOP.NAVY.missions","Eliminated the stolen Constrictor.");
				this._add("GALCOP.NAVY.kills","++",1);
				this.$config.updCR = 0;
				break;
			case "oolite-thargoid-plans-briefing1":
				this._addFortesque();
				break;
			case "oolite-thargoid-plans-briefing2":
				this._add("LOGS.GENERIC.list","A job for the Navy to deliver plans.");
				this._addBlake();
				this.$config.updTP = 1;
				break;
			case "oolite-thargoid-plans-debriefing":
				this._add("GALCOP.NAVY.missions","Delivered Thargoid defence plans.");
				this._add("LOGS.GENERIC.list","Yeeehaa. Delivered the plans!");
				this.$config.updTP = 0;
				break;
			case "oolite-nova-hero":
			case "oolite-nova-disappointed":
			case "oolite-nova-ignored":
			case "oolite-nova-coward":
				this._add("PERSONS.GENERIC.info","Survived System Nova.");
				this.$config.updNova = 0;
				break;
		}
	}
};
this.playerCompletedContract = function(type,result,fee,contract){
	var txt,f;
	if(!fee){
		switch(type){
			case "passenger": txt = "Nooo!! "+contract.name+" did not pay anything."; break;
			case "parcel": txt = "Argh! Didn't get paid for "+contract.name+"."; break;
			case "cargo": txt = "Argh! Didn't get paid for "+contract.cargo_description+"."; break;
		}
	} else {
		f = formatCredits(fee/10,1);
		switch(type){
			case "passenger": txt = (result==="success"?"On arriving":"Even if late")+" "+contract.name+" paid "+f+" ₢."; break;
			case "parcel": txt = (result==="success"?"On arriving":"Even if late")+" I got "+f+" ₢ for delivering "+contract.name+"."; break;
			case "cargo": txt = (result==="success"?"On arriving":"Even if "+result)+" I got "+f+" ₢ for delivering "+contract.cargo_description+"."; break;
		}
	}
	if(txt) this._add("LOGS.GENERIC.list",txt);
};
this.playerEnteredNewGalaxy = function(){
	this.$delay.push(["LOGS.GENERIC.list","Ship jumped to galaxy "+(galaxyNumber+1)+"."]);
};
this.playerRescuedEscapePod = function(fee, reason, pilot){
	fee = formatCredits(fee,1);
	if(system.isInterstellarSpace) this.$delay.push(["LOGS.GENERIC.list","Rescued "+pilot.name+" between worlds"+(fee?" Got "+(fee/10)+" ₢.":".")]);
	else this.$delay.push(["LOGS.GENERIC.list","Rescued "+pilot.name+" at "+system.name+(fee?" Got "+(fee/10)+" ₢.":".")]);
};
this.shipLaunchedEscapePod = function(){
	if(system.isInterstellarSpace) this.$delay.push(["LOGS.GENERIC.list","Nargh! Had to bail out to nowhere."]);
	else this.$delay.push(["LOGS.GENERIC.list","Nargh! Had to bail out at "+system.name+"."]);
};
this.shipWillDockWithStation = function(){
	this.$config.bounty = player.bounty;
	this.$config.credits = player.credits;
	if(player.rank!==this.$config.rank) this._add("LOGS.GENERIC.list","Rank of "+player.rank+" achieved!");
	this.$config.rank = player.rank;
	this.$config.status = player.legalStatus;
	this._addDelayed();
};
this.shipDockedWithStation = function(st){
	if(!player.ship.docked) return;
	var m;
	if(this.$config.credits!==player.credits){
		if(player.credits>this.$config.credits) m = "Yeah! Earned "+formatCredits(player.credits-this.$config.credits,1)+" ₢ at "+st.displayName;
		else m = "No! Lost "+formatCredits(this.$config.credits-player.credits,1)+" ₢ at "+st.displayName;
		if(system.isInterstellarSpace) m += ".";
		else m += " "+system.name+".";
		this._add("LOGS.GENERIC.list",m);
	}
	if(player.ship.markedForFines) this.$config.fined++;
};
this.playerBoughtNewShip = function(){
	this.$config.credits = player.credits;
};
// Note: Handler is fired (too?) early, so guarding (this.$init) is necessary!!!
this.equipmentAdded = function(eq){
	if(this.$init) return;
	this.$config.credits = player.credits;
	if(eq==="EQ_NAVAL_ENERGY_UNIT"){
		this._add("LOGS.GENERIC.list","Got a naval energy unit.");
		this._add("GALCOP.NAVY.awards","Naval energy unit.");
	}
	if(eq==="EQ_CLOAKING_DEVICE"){
		this.$delay.push(["LOGS.GENERIC.list","Got a cloaking device."]);
		this.$delay.push(["PERSONS.GENERIC.info","Found cloaking device."]);
	}
};
this.shipLaunchedFromStation = function(){
	this.$config.bounty = player.bounty;
	this.$config.credits = player.credits;
	this.$config.rank = player.rank;
	this.$config.status = player.legalStatus;
};
this.shipBountyChanged = function(delta){
	var m;
	if(this.$config.bounty===player.bounty+delta) return;
	if(!player.bounty) m = "Pfew. Clean again.";
	else {
		if(delta>0) m = "Ouch. Got a bounty.";
		if(delta<0) m = "Nice. They reduced my bounty.";
		if(this.$config.status!==player.legalStatus) m += " Now I'm "+player.legalStatus+".";
	}
	if(m) this.$delay.push(["LOGS.GENERIC.list",m]);
	this.$config.bounty = player.bounty;
	this.$config.status = player.legalStatus;
};
this.shipKilledOther = function(whom){
	if(player.rank!==this.$config.rank){
		this.$delay.push(["LOGS.GENERIC.list","Rank of "+player.rank+" achieved!"]);
		this.$config.rank = player.rank;
	}
	if(this.$config.updTP && whom && whom.isThargoid) this.$delay.push(["GALCOP.NAVY.kills","++",1]);
};
this.shipWillExitWitchspace = function(){
	if(system.isInterstellarSpace) this.$delay.push(["LOGS.GENERIC.list","Witchspace misjump occurred."]);
};
this._add = function(p,w,s){
	worldScripts.Lib_PAD._setPageEntry(p,w,s);
};
this._addDelayed = function(p,w,s){
	var l = this.$delay.length;
	if(l){
		for(var i=0;i<l;i++) worldScripts.Lib_PAD._setPageEntry(this.$delay[i][0],this.$delay[i][1],this.$delay[i][2]);
	}
	this.$delay.length = 0;
};
this._addPage = function(p,w,t,s){
	worldScripts.Lib_PAD._addPageInCategory(p,w,t,s);
};
this._addCurruthers = function(s){
	this._addPage("PERSONS.CAPTAIN CURRUTHERS",{name:"James Curruthers",origin:"Restricted data",species:"Human colonial",gender:"Male",age:45,ship:"Viper",rank:"Captain",t0:21,t1:"lib_ovc_curruthers.png",t2:8},["GALCOP.NAVY"],s);
};
this._addFortesque = function(s){
	this._addPage("PERSONS.CAPTAIN FORTESQUE",{name:"Peter Fortesque",origin:"Restricted data",species:"Human colonial",gender:"Male",age:41,ship:"Viper",rank:"Captain",t0:21,t1:"lib_ovc_fortesque.png",t2:8},["GALCOP.NAVY"],s);
};
this._addBlake = function(s){
	this._addPage("PERSONS.AGENT BLAKE",{name:"Paul Blake",origin:"Restricted data",species:"Restricted data",gender:"Male",age:34,ship:"Restricted data",rank:"Agent",t0:21,t1:"lib_ovc_blake.png"},["GALCOP.NAVY"],s);
};
}).call(this);
Scripts/Lib_Starmap.js
/* jshint bitwise:false, forin:false */
/* global mission,player,system,worldScripts,Vector3D */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "Lib_Starmap";

this.startUpComplete = function(){
	worldScripts.Lib_GUI.$IDRules.Lib_Starmap = {mus:1};
	this.$defs = {
		off: Vector3D([0,0,0]),
		rel: Vector3D([0,0,0])
	};
	this._update();
	this.$VSE = null;
	this.$OP = worldScripts.Lib_Main._lib.ooShaders();
	this.$POI = null;
};
/** function _start( obj ) - System map
* obj - Object
*  ini - Array. Max 12 entries.
*    String or Object { map:TEXTURE,ent:ENTITY [,col:Number] }
*  message - Optional. String. Initial message text.
*  title - Optional. String. Screen title.
*  ani - Optional. Animation for Lib_Animator. Requires capture flag!
*  POI - Optional. Vector. Point of interest.
*  MUL - Optional. Number. Multiplier. Default 0.00015.
* $return -
*/
this._start = function(obj,force){
	if(!obj || (this.$VSE && this.$VSE.isValid) || !this.$OP) return false;
	if(force) this._update();
	var absZ,col,fin,i,map,mat,md,mul,pos,tmp,rl, trck = [],
		maxZ = 0, maxY = 0, minZ = 0, minY = 0, mag = 0, sd = 0,
		dck = player.ship.docked, pls = 0, plm = 0, st = 0, as = 0;
	this.$POI = null;
	if(obj.POI){
		this.$POI = obj.POI;
		rl = obj.POI;
	} else rl = this.$defs.rel;
	if(obj.MUL) mul = obj.MUL;
	else mul = 0.00015;
	if(dck){
		var head = {
			choices:{ZZZ:"Continue"},
			message:obj.message?obj.message:"",
			background:{name:"lib_starmap_bg.png",height:512},
			screenID:"Lib_Starmap",
			model:"lib_ms_helper12",
			spinModel:false,
			title:obj.title?obj.title:"System Map"
		};
		this.$HUD = player.ship.hudHidden;
		player.ship.hudHidden = true;
		mission.runScreen(head,this._choices);
		md = mission.displayModel;
		md.orientation = [1,0,1,0];
		md.position = [0,0,500];
	} else {
		if(!this.$wp || (this.$wp && !this.$wp.isValid)) this.$wp = system.addVisualEffect("lib_starmap_wp",[0,0,0]);
		md = system.addVisualEffect("lib_starmap12",player.ship.position);
		md.script.$defs = rl;
		md.script.$mul = mul;
	}
	md.lightsActive = false;
	if(obj){
		for(i=0;i<12;i++){
			mat = md.subEntities[i].getMaterials();
			col = 0;
			if(i>obj.ini.length-1){
				mat["lib_null.png"].textures[0] = "lib_blend0.png";
			} else {
				if(typeof(obj.ini[i])==="string"){
					pos = this.$defs.off;
					map = "lib_blend0.png";
					switch(obj.ini[i]){
						case "PS": map = "lib_starmap_5.png"; pos = player.ship.position; trck.push({ent:player.ship,sub:i}); break;
						case "S": if(system.sun){map = "lib_starmap_0.png"; pos = system.sun.position; trck.push({ent:system.sun,sub:i});} break;
						case "P": tmp = this._getPlanet(pls); pls++; if(tmp){map = tmp[0]; pos = tmp[1].position; trck.push({ent:tmp[1],sub:i});} break;
						case "M": tmp = this._getMoon(plm); plm++; if(tmp){map = tmp[0]; pos = tmp[1].position; trck.push({ent:tmp[1],sub:i});} break;
						case "ST": if(this.$st.sta){map = "lib_starmap_3.png"; pos = this.$st.sta.position; trck.push({ent:this.$st.sta,sub:i});} break;
						case "STG": tmp = this._getGalStation(st); st++; if(tmp){map = tmp[0]; pos = tmp[1].position; trck.push({ent:tmp[1],sub:i});
							if(tmp[1].isPolice) col = 4; else col = 3;} break;
						case "WP": map = "lib_starmap_2.png"; trck.push({ent:this.$wp,sub:i}); break;
						case "H": if(this.$st.sth.length){map = "lib_starmap_4.png"; pos = this.$st.sth[0].position; trck.push({ent:this.$st.sth[0],sub:i});} break;
						case "AS": tmp = this._getAsteroidField(as); as += 20; if(tmp){map = tmp[0]; pos = tmp[1].position; trck.push({ent:tmp[1],sub:i});} break;
					}
				} else {
					if(obj.ini[i].ent){
						pos = obj.ini[i].ent.position;
						trck.push({ent:obj.ini[i].ent,sub:i});
					} else {
						if(dck) pos = obj.ini[i].pos;
						else continue;
					}
					map = obj.ini[i].map;
					if(obj.ini[i].col) col = obj.ini[i].col;
				}
				if(col) mat["lib_null.png"].uniforms.MC.value = this._getColor(col);
				mat["lib_null.png"].textures[0] = map;
				pos = this._compress(pos);
				// Scale down for display
				fin = pos.subtract(rl).multiply(mul);
				if(dck){
					if(fin.z>maxZ) maxZ = fin.z;
					if(fin.y>maxY) maxY = fin.y;
					if(fin.z<minZ) minZ = fin.z;
					if(fin.y<minY) minY = fin.y;
				} else {
					mag = fin.magnitude();
					if(mag>110) sd = (mag-100)*0.1;
				}
				md.subEntities[i].position = fin;
			}
			md.subEntities[i].setMaterials(mat);
		}
		if(dck){
			// Auto zoom
			absZ = Vector3D([(-minZ)+maxZ,(-minY)+maxY,0]).magnitude();
			absZ = Math.max(absZ,180);
			md.position = [0,0,absZ*2];
			// Animation
			if(obj.ani) worldScripts.Lib_Animator._start(obj.ani);
		} else {
			if(sd) md.script.$mul = mul/sd;
			// Track
			if(trck.length) md.script.$upd = trck;
			if(player.ship.compassTarget) md.orientation = player.ship.compassTarget.position.rotationTo(rl);
			else md.orientation = player.ship.position.rotationTo(rl);
			this.$VSE = md;
			this.$EnableTimer = new Timer(this,this._doEnableTimer,1);
		}
	}
	return true;
};
this._getPlanet = function(n){
	if(!n){
		if(!this.$pl.pla) return null;
		return(["lib_starmap_1.png",this.$pl.pla]);
	}
	if(this.$pl.plp.length<=n-1) return null;
	return(["lib_starmap_P1.png",this.$pl.plp[n-1]]);
};
this._getMoon = function(n){
	if(this.$pl.plm.length<=n) return null;
	return(["lib_starmap_M1.png",this.$pl.plm[n]]);
};
this._getGalStation = function(n){
	if(this.$st.stgal.length<=n) return null;
	if(this.$st.stgal[n].maxSpeed>5) return(["lib_starmap_i4.png",this.$st.stgal[n]]);
	return(["lib_starmap_i5.png",this.$st.stgal[n]]);
};
this._getAsteroidField = function(n){
	if(this.$as.length<=n) return null;
	return(["lib_starmap_7.png",this.$as[n]]);
};
this._getColor = function(n){
	var c;
	switch(n){
		case 1: c = [0.8,0,0,1]; break; // red
		case 2: c = [0,0.8,0,1]; break; // green
		case 3: c = [0,0,0.8,1]; break; // blue
		case 4: c = [0.6,0,0.8,1]; break; // purple
		case 5: c = [0.4,0.4,0.4,1]; break; // gray
		case 6: c = [0,0.7,0.7,1]; break; // aqua
		case 7: c = [0.8,0.6,0,1]; break; // amber
		case 8: c = [0.8,0.6,0.3,1]; break; // gold
		case 9: c = [0.3,0,0.5,1]; break; // UV
		default: c = [1,1,1,1]; break; // white
	}
	return c;
};
this._addInFreeSlot = function(ent,map,col){
	if(!this.$VSE || !this.$VSE.isValid) return;
	var x = this.$VSE.script.$upd,m=x.length,slot,mat;
	for(var i=0;i<12;i++){
		if(i>m-1 || !x[i].ent){slot = i; break;}
	}
	if(typeof(slot)!=="number" || slot>11) return false;
	mat = this.$VSE.subEntities[slot].getMaterials();
	mat["lib_null.png"].textures[0] = map;
	mat["lib_null.png"].uniforms.MC.value = this._getColor(col);
	if(slot<=m) x[slot] = {ent:ent,sub:slot};
	else x.push({ent:ent,sub:slot});
	this.$VSE.script.$upd = x;
	this.$VSE.subEntities[slot].setMaterials(mat);
	this.$VSE.subEntities[slot].shaderVector1 = [0,0,0];
	return true;
};
// Inflight options
this._doEnableTimer = function(){
	if(player.alertCondition<3 && !player.ship.weaponsOnline){
		this._switchOn();
		if(!this.$VSE || !this.$VSE.isValid) return;
		this.$VSE.shaderFloat1 = 1;
	} else this._switchOff();
	this.$EnableTimer = null;
};
this.alertConditionChanged = function(s){
	if(s>2) this._switchOff();
	else if(s>0 && !player.ship.weaponsOnline) this._switchOn();
};
this.compassTargetChanged = function(){
	if(!this.$VSE || !this.$VSE.isValid) return;
	if(this.$POI) this.$VSE.orientation = player.ship.compassTarget.position.rotationTo(this.$POI);
	else this.$VSE.orientation = player.ship.compassTarget.position.rotationTo(this.$defs.rel);
};
this.weaponsSystemsToggled = function(state){
	if(state) this._switchOff();
	else if(player.alertCondition<3) this._switchOn();
};
this._switchOn = function(){
	if(!this.$VSE || !this.$VSE.isValid) return;
	this.$VSE.script.$hide = true;
	if(!this.$EnableTimer) this.$EnableTimer = new Timer(this,this._doEnableTimer,1);
};
this._switchOff = function(){
	if(!this.$VSE || !this.$VSE.isValid) return;
	this.$VSE.shaderFloat1 = 0;
	this.$VSE.script.$hide = false;
};
this._toggleMP = function(){
	if(!this.$VSE || !this.$VSE.isValid) return;
	this.$VSE.script.$PSC = !this.$VSE.script.$PSC;
};
// Update after populator
this.shipExitedWitchspace = this.shipWillDockWithStation = this.shipWillLaunchFromStation = function(){
	this._update();
};
this._update = function(){
	if(this.$VSE && this.$VSE.isValid) this.$VSE.remove();
	this.$VSE = null;
	this._updPlanets();
	this._updStations();
	this._updAsteroids();
	this._getMidpoint();
};
this._updPlanets = function(){
	var pl = system.planets;
	this.$pl = {pla:null,plp:[],plm:[]};
	for(var i=0;i<pl.length;i++){
		if(pl[i].isMainPlanet) this.$pl.pla = pl[i];
		else {
			if(pl[i].hasAtmosphere) this.$pl.plp.push(pl[i]);
			else this.$pl.plm.push(pl[i]);
		}
	}
};
this._updStations = function(){
	var st = system.stations;
	this.$st = {sta:null,sth:[],stgal:[],stoth:[]};
	for(var i=0;i<st.length;i++){
		if(st[i].isMainStation) this.$st.sta = st[i];
		else if(st[i].isRock && st[i].name==="Rock Hermit") this.$st.sth.push(st[i]);
		else {
			switch(st[i].allegiance){
				case "galcop":
				case "hunter":
				case "neutral": this.$st.stgal.push(st[i]); break;
				default: if(st[i].beaconCode) this.$st.stoth.push(st[i]);
			}
		}
	}
};
this._updAsteroids = function(){
	this.$as = system.filteredEntities(this,function(entity){return entity.isShip && entity.isRock && entity.name==="Asteroid";},player.ship);
};
this._getMidpoint = function(){
	if(system.isInterstellarSpace){
		this.$defs.rel = this.$defs.off;
		return;
	} else {
		// no c as WP is [0,0,0]
		var a = this._compress(system.sun.position), b = this._compress(system.mainPlanet.position);
		this.$defs.rel = Vector3D([(a.x+b.x)/3,(a.y+b.y)/3,(a.z+b.z)/3]);
	}
};
// Compress far out
this._compress = function(pos){
	var pmag = pos.magnitude();
	if(pmag>1199998.8) pos = Vector3D.interpolate(this.$defs.off,pos,1199998.8/pmag);
	return pos;
};
this._choices = function(choice){worldScripts.Lib_Starmap._choiceEval(choice); return;};
this._choiceEval = function(choice){
	player.ship.hudHidden = this.$HUD;
};
}).call(this);
Scripts/Lib_Starmap.txt
// Display traders
var a = system.filteredEntities(this,function(entity){return entity.isShip && !entity.isStation && !entity.owner && entity.isTrader;},player.ship),b=["PS","WP","P","ST","S"];
for(var i=0;i<7;i++){if(a.length>i){b.push({map:"lib_starmap_i2.png",ent:a[i],col:5});}}worldScripts.Lib_Starmap._start({ini:b});

// Display pirates
var a = system.filteredEntities(this,function(entity){return entity.isShip && !entity.isStation && !entity.owner && entity.isPirate;},player.ship),b=["PS","WP","P","ST","S"];
for(var i=0;i<7;i++){if(a.length>i){b.push({map:"lib_starmap_i3.png",ent:a[i],col:1});}}worldScripts.Lib_Starmap._start({ini:b});

// Display police
var a = system.filteredEntities(this,function(entity){return entity.isShip && !entity.isStation && !entity.owner && entity.isPolice;},player.ship),b=["PS","WP","P","ST","S"];
for(var i=0;i<7;i++){if(a.length>i){b.push({map:"lib_starmap_i1.png",ent:a[i],col:4});}}worldScripts.Lib_Starmap._start({ini:b});

// Default - Player, Witchpoint, Planet, Sun, MainStation, Hermit, Asteroid fields and secondary stations
worldScripts.Lib_Starmap._start( { ini:["PS","WP","P","S","ST","H","AS","AS","AS","STG","STG","STG"] } );

// Display fun
var a = system.filteredEntities(this,function(entity){return entity.isShip && !entity.isStation && !entity.owner && entity.isTrader},player.ship),
	c = system.filteredEntities(this,function(entity){return entity.isShip && !entity.isStation && !entity.owner && entity.isPirate && entity.group && entity.group.leader && entity.entityPersonality===entity.group.leader.entityPersonality;},player.ship),
	b=["PS","WP","P","ST","S"];
for(var i=0;i<15;i++){
	if(b.length===12) break;
	if((i&1) && a.length>i){b.push({map:"lib_starmap_i2.png",ent:a[i],col:5});}
	else if(c.length>i){b.push({map:"lib_starmap_i2.png",ent:c[i],col:1});}
}
worldScripts.Lib_Starmap._start({ini:b});
Scripts/lib_conditions.js
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
"use strict";
this.name = "lib_conditions";

this.allowSpawnShip = function(shipKey){
	return true;
};
Scripts/lib_fx.js
/* global addFrameCallback,player,removeFrameCallback */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "lib_fx";

this.$time = 0;
// Set on spawning
this.$repos = 250; 
this.$reposTil = 0;
this.$ridTime = 4;
this.$look = 0;
this.$view = 0;
this.$offset = 0;
this.$glare = player.ship.sunGlareFilter;
player.ship.sunGlareFilter = 1;

this.effectSpawned = function(){
	this.$fcb = addFrameCallback(this._repos.bind(this));
};
this.effectRemoved = function(){
	removeFrameCallback(this.$fcb);
};
this._repos = function(delta){
	var ps = player.ship,vF=ps.vectorForward,lv,v,pos,npos,sgf;
	if(!ps.isValid || this.$time>this.$ridTime){
		this.visualEffect.remove();
		return;
	}
	this.$time += delta;
	if(!delta) return;
	if(this.$glare<1 && this.$time>this.$reposTil-1){
		if(ps.sunGlareFilter-delta>this.$glare) ps.sunGlareFilter -= delta;
		else ps.sunGlareFilter = this.$glare;
	}
	if(this.$reposTil && this.$time>this.$reposTil) this.$repos = 0;
	if(this.$repos){
		if(this.$view){
			switch(ps.viewDirection){
				case "VIEW_AFT": pos = ps.position.add(ps.viewPositionAft); break;
				case "VIEW_PORT": pos = ps.position.add(ps.viewPositionPort); break;
				case "VIEW_STARBOARD": pos = ps.position.add(ps.viewPositionStarboard); break;
				case "VIEW_FORWARD": pos = ps.position.add(ps.viewPositionForward); break;
				default: pos = ps.position;
			}
			pos = pos.add(vF.multiply(this.$repos));
		} else pos = ps.position.add(vF.multiply(this.$repos)).add(ps.vectorUp.multiply(this.$offset));
		this.visualEffect.position = pos;
		this.visualEffect.orientation = ps.orientation;
		if(this.$look){
			switch(ps.viewDirection){
				case "VIEW_AFT": lv = [0,0,-1.07]; break;
				case "VIEW_PORT": lv = [-3,0,1.4]; break;
				case "VIEW_STARBOARD": lv = [3,0,1.4]; break;
				case "VIEW_FORWARD": lv = [0,0,1.4]; break;
				default: lv = [0,0,1.4];
			}
			this.visualEffect.shaderVector2 = lv;
		}
	}
};
}).call(this);
Scripts/lib_shield.js
"use strict";
this.name = "lib_shield";

this.$fcb;
this.$parent;

this.effectSpawned = function(){
	this.$fcb = addFrameCallback(this._updatePosition.bind(this));
};
this.effectRemoved = function(){
	removeFrameCallback(this.$fcb);
};
this._updatePosition = function(){
	if(!this.$parent) return;
	if(player.ship.position){
		this.visualEffect.position = this.$parent.position.subtract(this.$parent.vectorForward.multiply(-120));
		this.visualEffect.position = this.visualEffect.position.add(this.$parent.position.subtract(player.ship.position).direction().multiply(3));
	} else removeFrameCallback(this.$fcb);
};
Scripts/lib_starmap12.js
/* global addFrameCallback,player,removeFrameCallback,Vector3D */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 */
(function(){
"use strict";
this.name = "lib_starmap12";

this.$upd = [];
this.$c = 0;
this.$pos = null;
this.$pmag = 0;
this.$ps = player.ship;
this.$defs = null;
this.$mul = 0.00015;
this.$hide = true;
this.$PSC = 0;
this.effectSpawned = function(){
	this.$fcb = addFrameCallback(this._repos.bind(this));
};
this.effectRemoved = function(){
	removeFrameCallback(this.$fcb);
};
this._repos = function(){
	if(!this.$ps.isValid || !this.$ps.isInSpace){
		this.visualEffect.remove();
		return;
	}
	if(!this.$defs || (!this.visualEffect.shaderFloat1 && !this.$hide)) return;
	else {
		var ul = this.$upd.length;
		this.visualEffect.position = this.$ps.position.add(this.$ps.vectorForward.multiply(400)).add(this.$ps.vectorUp.multiply(80)).add(this.$ps.vectorRight.multiply(80));
		switch(this.$c){
			case 0: if(ul) this._updatePos(this.$upd[0]); break;
			case 1: if(ul>1) this._updatePos(this.$upd[1]); break;
			case 2: if(ul>2) this._updatePos(this.$upd[2]); break;
			case 3: if(ul>3) this._updatePos(this.$upd[3]); break;
			case 4: if(ul>4) this._updatePos(this.$upd[4]); break;
			case 5: if(ul>5) this._updatePos(this.$upd[5]); break;
			case 6: if(ul>6) this._updatePos(this.$upd[6]); break;
			case 7: if(ul>7) this._updatePos(this.$upd[7]); break;
			case 8: if(ul>8) this._updatePos(this.$upd[8]); break;
			case 9: if(ul>9) this._updatePos(this.$upd[9]); break;
			case 10: if(ul>10) this._updatePos(this.$upd[10]); break;
			case 11: if(ul>11) this._updatePos(this.$upd[11]); break;
		}
		this.$c++;
		this.$c %= 12;
	}
};
this._updatePos = function(obj){
	if(!obj.ent) return;
	if(!obj.ent.isValid || !obj.ent.isInSpace){
		this.visualEffect.subEntities[obj.sub].shaderVector1 = [0,0,1];
		obj.ent = null;
	} else {
		this.$pos = obj.ent.position;
		this.$pmag = this.$pos.magnitude();
		if(this.$PSC){
			if(this.$pmag>1199998.8) this.$pos = Vector3D.interpolate(this.$ps.position,this.$pos,1199998.8/this.$pmag);
			this.visualEffect.subEntities[obj.sub].position = this.$pos.subtract(this.$ps.position).multiply(this.$mul);
		} else {
			if(this.$pmag>1199998.8) this.$pos = Vector3D.interpolate(this.$defs,this.$pos,1199998.8/this.$pmag);
			this.visualEffect.subEntities[obj.sub].position = this.$pos.subtract(this.$defs).multiply(this.$mul);
		}
	}
};
}).call(this);
Scripts/lib_test.js
/* global system,worldScripts,Timer */
/* (C) Svengali 2016-2018, License CC-by-nc-sa-4.0 - Detect changes in custom role entity */
(function(){
"use strict";
this.name = "lib_test";

// There are a few AddOns which are changing / removing custom role entities. Uncalled!

this.shipSpawned = function(){
	delete this.shipSpawned;
	this.$checkTimer = new Timer(this,this._doCheck,0.5);
};
this._stopTimers = function(warn){
	if(this.$checkTimer) this.$checkTimer.stop();
	this.$checkTimer = null;
	return;
};
this._doCheck = function _doCheck(){
	var eq = {
		accuracy: -2,
		autoAI: false,
		autoWeapons: false,
		bounty: 0,
		cloakAutomatic: false,
		energyRechargeRate: 3,
		fuel: 6,
		heatInsulation: 1,
		isBeacon: false,
		isCloaked: false,
		isDerelict: false,
		isJamming: false,
		isPirate: false,
		isPirateVictim: false,
		isPolice: false,
		isTrader: false,
		maxEnergy: 450,
		maxEscorts: 2,
		missileCapacity: 4,
		missileLoadTime: 4,
		name: "Boa",
		shipClassName: "Boa"
	},
	eqs = {
		aftWeapon: ["EQ_WEAPON_PULSE_LASER"],
		equipment: ["EQ_FUEL_SCOOPS","EQ_ESCAPE_POD"],
		forwardWeapon: ["EQ_WEAPON_PULSE_LASER"],
		missiles: ["EQ_MISSILE","EQ_MISSILE","EQ_MISSILE"],
		portWeapon: ["EQ_WEAPON_NONE"],
		starboardWeapon: ["EQ_WEAPON_NONE"]
	},
	wps = ["weaponPositionForward","weaponPositionAft","weaponPositionPort","weaponPositionStarboard"],
	listA = Object.keys(eq), lA = listA.length,
	listB = Object.keys(eqs), lB = listB.length,
	i,j,k,cur, warn = worldScripts.Lib_Main._lib.$entCstChanged;
	for(i=0;i<lA;i++) if(this.ship[listA[i]] !== eq[listA[i]] && warn.indexOf(listA[i])===-1) warn.push(listA[i]);
	for(i=0;i<lB;i++){
		cur = this.ship[listB[i]];
		if(cur.length){
			for(j=0;j<cur.length;j++) if(cur[j].equipmentKey !== eqs[listB[i]][j] && warn.indexOf(listB[i])===-1) warn.push(cur[j].equipmentKey);
		} else if(cur.equipmentKey !== eqs[listB[i]][0] && warn.indexOf(listB[i])===-1) warn.push(listB[i]);
	}
	for(k=0;k<4;k++) if(this.ship[wps[k]].length>1 && warn.indexOf(wps[k])===-1) warn.push(wps[k]);
	if(145!==this.shipDied.toSource().length+this.shipRemoved.toSource().length) worldScripts.Lib_Main._lib.$entCstPatched = true;
	this._stopTimers();
};
this.shipDied = function(){this._stopTimers();};
this.shipRemoved = function(){this._stopTimers(); worldScripts.Lib_Main._lib.$entCstRemoved = true;};
this.entityDestroyed = function(){this._stopTimers();};
this.shipLaunchedEscapePod = function(){this._stopTimers();};
this.shipWillEnterWormhole = function(){this._stopTimers();};
this.playerWillEnterWitchspace = function(){this._stopTimers();};
}).call(this);