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

Expansion Ship's Library

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description An e-reader for your ship which can read various books while docked - or with a purchasable upgrade, also while in flight. It comes with a ship's manual as an initial book, with other books available as separate expansion packs. An e-reader for your ship which can read various books while docked - or with a purchasable upgrade, also while in flight. It comes with a ship's manual as an initial book, with other books available as separate expansion packs.
Identifier oolite.oxp.cim.ships-library oolite.oxp.cim.ships-library
Title Ship's Library Ship's Library
Category Equipment Equipment
Author cim cim
Version 0.9 0.9
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL https://wiki.alioth.net/index.php/Ship%27s_Library_OXP n/a
Download URL https://wiki.alioth.net/img_auth.php/e/eb/Ships_Library_0.9.oxz http://com.sotl.org.ukgames/files/oolite/Ships_Library_0.8.oxz
License GPL v2+ / CC-BY-NC-SA 3.0 GPL v2+ / CC-BY-NC-SA 3.0
File Size n/a
Upload date 1710799948

Documentation

Also read http://wiki.alioth.net/index.php/Ship's%20Library

Equipment

Name Visible Cost [deci-credits] Tech-Level
Ship's Library Flight Interface yes 0 5+

Ships

This expansion declares no ships.

Models

This expansion declares no models.

Scripts

Path
Scripts/ingame-manual.js
"use strict";
this.author      = "cim"; 
this.copyright   = "� 2011-2014 cim."; 
this.licence = "CC-BY-SA 3.0";
this.version     = "0.8"; 
this.name = "Ships Library";
this.description = "An e-book library readable on the F4 screen";

/* Public */
this._registerBook = function(key,title,contents) {
	this.$books[key] = {title: title, contents: contents};
}

this._unregisterBook = function(key) {
	if (key == "ingame-manual") {
		return false; // can't remove this one
	}
	if (this.$currentBook == key) {
		if (mission.screenID == "ships-library") {
			// book is currently loaded and mission screen active
			return false;
		}
		this._setBook("ingame-manual");
	}
	delete this.$books[key];
	return true;
}

this.startUp = function() {
	delete this.startUp;

	this.$fcbM = false;
	this.$hudHidden = false;
	this.$manualActive = false;
	this.$currentBook = false;
	this.$spaceWidth = defaultFont.measureString(" ");

	// settings for mission screen reading
	this.$chapter = 0;
	this.$offset = 0;
	this.$msStore = "offset";
	this.$msRows = 15; 
	this.$msCols = 32; 

	// settings for MFD in-flight reading
	this.$mfdChapter = 0;
	this.$mfdOffset = 0;
	this.$mfdStore = "miniOffset";
	this.$mfdRows = 10; 
	this.$mfdCols = 14.75; 
	
	this.$mfdMode = 0;
	this.$mfdOn = false;

	// initialise
	this.$minblock = 3;
	this.$chaptersperpage = 20;

	this.$books = new Object;
	this._registerIngameManual();
	this.$mode = "firstrun";

	if (missionVariables.ships_library_bookmark) {
		this.$bookmark = JSON.parse(missionVariables.ships_library_bookmark);
	} else {
		this.$bookmark = {"ingame-manual":[0,0,0,0]};
	}

	this._setBook("ingame-manual");
	
}

this.startUpComplete = function() {
	this._initInterface(system.mainStation);
	this._initInterface(player.ship.dockedStation);
}


/* Private and handlers */

this.playerWillSaveGame = function() {
	missionVariables.ships_library_bookmark = JSON.stringify(this.$bookmark);
}

this.shipDockedWithStation = function(station) {
	this._initInterface(station);
}

this._initInterface = function(station) {
	station.setInterface(this.name,{
		title:expandMissionText("ships-library-title"),
		category:expandDescription("[interfaces-category-ship-systems]"),
		summary:expandMissionText("ships-library-summary"),
		callback:this._showLibrary.bind(this)
	});
}

this._registerIngameManual = function() {
	var contents = [
		{level: 0, key: "ingame-manual-frontpage", params:[this._getTip], opts:{model:"["+player.ship.dataKey+"]", spinModel:false}},
		{level: 0, key: "ingame-manual-welcome"},
		{level: 0, key: "ingame-manual-controls"},
		{level: 1, key: "ingame-manual-ship-systems"},
		{level: 2, key: "ingame-manual-ship-systems-status", backgrounds: ["ingame-manual-f5.png",""]},
		{level: 2, key: "ingame-manual-ship-systems-manifest", backgrounds: ["ingame-manual-f5f5.png",""]},
		{level: 2, key: "ingame-manual-ship-systems-src", backgrounds: ["ingame-manual-f6.png",""]},
		{level: 2, key: "ingame-manual-ship-systems-lrc", backgrounds: ["ingame-manual-f6f6.png",""]},
		{level: 2, key: "ingame-manual-ship-systems-planet", backgrounds: ["ingame-manual-f7.png",""]},
		{level: 2, key: "ingame-manual-ship-systems-commodity",params:[function(){return player.ship.cargoSpaceCapacity;}], backgrounds: ["ingame-manual-f8.png",""]},
		{level: 1, key: "ingame-manual-station-systems"},
		{level: 2, key: "ingame-manual-station-systems-launch", backgrounds: ["ingame-manual-f1.png"]},
		{level: 2, key: "ingame-manual-station-systems-equipment", backgrounds: ["ingame-manual-f3.png",""]},
		{level: 2, key: "ingame-manual-station-systems-shipyard", backgrounds: ["ingame-manual-f3f3.png",""]},
		{level: 2, key: "ingame-manual-station-systems-interfaces", backgrounds: ["ingame-manual-f4.png",""]},
		{level: 1, key: "ingame-manual-inflight"},
		{level: 2, key: "ingame-manual-inflight-view"},
		{level: 2, key: "ingame-manual-inflight-hud",params:[function(){return Math.max(1,Math.floor(player.ship.maxEnergy/64));}], backgrounds: ["ingame-manual-hud.png"]},
		{level: 3, key: "ingame-manual-inflight-hud-scanner", backgrounds: ["ingame-manual-hud.png"]},
		{level: 2, key: "ingame-manual-inflight-flight"},
		{level: 2, key: "ingame-manual-inflight-docking", backgrounds: ["ingame-manual-dock0.png","ingame-manual-dock1.png","ingame-manual-dock2.png","ingame-manual-dock3.png","ingame-manual-dock4.png"]},
		{level: 2, key: "ingame-manual-inflight-navigation",params:[function(){if (player.ship.hasHyperspaceMotor) { return expandDescription("You have already been introduced to the short range chart for selecting a witchspace destination. Provided the destination is within range, you have sufficient fuel, and you are sufficiently clear of large objects for safety reasons, you can activate the witchdrive by pressing '[oolite_key_hyperspace]'. The drive will take "+player.ship.hyperspaceSpinTime+" seconds to power up and prepare for the jump. During this time it is recommended to maintain level flight. Modern drives are considerably more reliable than their predecessors in this regard, but extreme manoeuvres can still cause drive failures occasionally.\n\n");} else {return ""}},function(){if(player.ship.hasHyperspaceMotor) { return "If you are out of fuel";} else {return "As your ship does not have a witchdrive fitted"}}]},
		{level: 2, key: "ingame-manual-inflight-combat"},
		{level: 3, key: "ingame-manual-inflight-combat-laser", params:[function(){var m=player.ship.weaponFacings;return (m&1)+((m&2)/2)+((m&4)/4)+((m&8)/8);}], backgrounds: ["ingame-manual-laser0.png"]},
		{level: 3, key: "ingame-manual-inflight-combat-missile", params:[function(){return player.ship.missileCapacity;}], backgrounds: ["ingame-manual-missile0.png"]},
		{level: 0, key: "ingame-manual-equipment"},
		{level: 1, key: "ingame-manual-equipment-standard"},
		{level: 2, key: "ingame-manual-equipment-standard-hull"},
		{level: 2, key: "ingame-manual-equipment-standard-generators"},
		{level: 2, key: "ingame-manual-equipment-standard-capacitors", params:[function(){return Math.ceil(player.ship.maxEnergy/64);}]},
		{level: 2, key: "ingame-manual-equipment-standard-support"},
		{level: 2, key: "ingame-manual-equipment-standard-crew"},
		{level: 2, key: "ingame-manual-equipment-standard-engines"},
		{level: 2, key: "ingame-manual-equipment-standard-shields"},
		{level: 2, key: "ingame-manual-equipment-standard-sensors"},
		{level: 2, key: "ingame-manual-equipment-standard-cargo"},
		{level: 2, key: "ingame-manual-equipment-standard-witchdrive"},
		{level: 1, key: "ingame-manual-equipment-trading"},
		{level: 2, key: "ingame-manual-equipment-trading-cargo"},
		{level: 2, key: "ingame-manual-equipment-trading-scoops", backgrounds: ["ingame-manual-scoops0.png","ingame-manual-scoops1.png"]},
		{level: 2, key: "ingame-manual-equipment-trading-heat", backgrounds: ["ingame-manual-heat.png"]},
		{level: 2, key: "ingame-manual-equipment-trading-passenger"},
		{level: 2, key: "ingame-manual-equipment-trading-ana", backgrounds: ["ingame-manual-ana.png"]},
		{level: 2, key: "ingame-manual-equipment-trading-asc", backgrounds: ["ingame-manual-asc.png"]},
		{level: 2, key: "ingame-manual-equipment-trading-docking"},
		{level: 2, key: "ingame-manual-equipment-trading-galactic"},
		{level: 1, key: "ingame-manual-equipment-combat"},
		{level: 2, key: "ingame-manual-equipment-combat-lasers", backgrounds: ["ingame-manual-laser1.png"]},
		{level: 3, key: "ingame-manual-equipment-combat-lasers-pulse"},
		{level: 3, key: "ingame-manual-equipment-combat-lasers-beam"},
		{level: 3, key: "ingame-manual-equipment-combat-lasers-mining"},
		{level: 3, key: "ingame-manual-equipment-combat-lasers-military"},
		{level: 2, key: "ingame-manual-equipment-combat-pylons", backgrounds: ["ingame-manual-pylons0.png"]}, 
		{level: 3, key: "ingame-manual-equipment-combat-pylons-missile", backgrounds: ["ingame-manual-missile1.png"]},
		{level: 3, key: "ingame-manual-equipment-combat-pylons-hardhead", opts:{model:"[ecm-proof-missile]"}},
		{level: 3, key: "ingame-manual-equipment-combat-pylons-qcm", opts:{model:"[qbomb]"}},
		{level: 2, key: "ingame-manual-equipment-combat-ecm"},
		{level: 2, key: "ingame-manual-equipment-combat-mts", backgrounds: ["ingame-manual-pylons1.png"]},
		{level: 2, key: "ingame-manual-equipment-combat-escape", opts:{model:"[escape-capsule]"}},
		{level: 2, key: "ingame-manual-equipment-combat-eeu"},
		{level: 2, key: "ingame-manual-equipment-combat-tsme"},
		{level: 2, key: "ingame-manual-equipment-combat-inject", backgrounds: ["ingame-manual-inject.png"]},
		{level: 2, key: "ingame-manual-equipment-combat-wormhole", backgrounds: ["ingame-manual-wormhole.png"]},
		{level: 2, key: "ingame-manual-equipment-combat-ste", backgrounds: ["ingame-manual-ste.png"]},
		{level: 2, key: "ingame-manual-equipment-combat-its"},
		{level: 2, key: "ingame-manual-equipment-combat-shield"},
		{level: 1, key: "ingame-manual-equipment-extra"},
		{level: 2, key: "ingame-manual-equipment-extra-displays"},
		{level: 0, key: "ingame-manual-sociology"},
		{level: 1, key: "ingame-manual-sociology-galcop"},
		{level: 2, key: "ingame-manual-sociology-galcop-facilities", backgrounds: ["ingame-manual-station.png"]},
		{level: 2, key: "ingame-manual-sociology-galcop-legal", backgrounds: ["ingame-manual-police0.png","ingame-manual-police1.png","ingame-manual-police0.png"]},
		{level: 2, key: "ingame-manual-sociology-galcop-combat", backgrounds: ["ingame-manual-combat0.png"]},
		{level: 1, key: "ingame-manual-sociology-pilots"},
		{level: 2, key: "ingame-manual-sociology-pilots-traders", backgrounds: ["ingame-manual-shiptrader.png"]},
		{level: 2, key: "ingame-manual-sociology-pilots-hunters", backgrounds: ["ingame-manual-shiphunter.png"]},
		{level: 2, key: "ingame-manual-sociology-pilots-pirates", backgrounds: ["ingame-manual-shippirate.png"]},
		{level: 2, key: "ingame-manual-sociology-pilots-miners", backgrounds: ["ingame-manual-shipminer.png"]},
		{level: 2, key: "ingame-manual-sociology-pilots-military"},
		{level: 2, key: "ingame-manual-sociology-pilots-deep"},
		{level: 2, key: "ingame-manual-sociology-pilots-salvage"},
		{level: 1, key: "ingame-manual-sociology-govt"},
		{level: 2, key: "ingame-manual-sociology-govt-corp"},
		{level: 2, key: "ingame-manual-sociology-govt-demo"},
		{level: 2, key: "ingame-manual-sociology-govt-conf"},
		{level: 2, key: "ingame-manual-sociology-govt-comm"},
		{level: 2, key: "ingame-manual-sociology-govt-dict"},
		{level: 2, key: "ingame-manual-sociology-govt-mult"},
		{level: 2, key: "ingame-manual-sociology-govt-feud"},
		{level: 2, key: "ingame-manual-sociology-govt-anar"},
		{level: 1, key: "ingame-manual-sociology-econ"},
		{level: 2, key: "ingame-manual-sociology-econ-agri"},
		{level: 2, key: "ingame-manual-sociology-econ-ind"},
		{level: 1, key: "ingame-manual-sociology-species"},
		{level: 1, key: "ingame-manual-sociology-thargoids"},
		{level: 1, key: "ingame-manual-sociology-history"},
		{level: 2, key: "ingame-manual-sociology-history-before"},
		{level: 2, key: "ingame-manual-sociology-history-eight"},
		{level: 2, key: "ingame-manual-sociology-history-interspecies"},
		{level: 2, key: "ingame-manual-sociology-history-founding"},
		{level: 2, key: "ingame-manual-sociology-history-thargoids"},
		{level: 2, key: "ingame-manual-sociology-history-modern"},
		{level: 0, key: "ingame-manual-ships"},
		{level: 1, key: "ingame-manual-ships-adder", opts:{model:"[adder]"}},
		{level: 1, key: "ingame-manual-ships-anaconda", opts:{model:"[anaconda]"}},
		{level: 1, key: "ingame-manual-ships-asp", opts:{model:"[asp]"}},
		{level: 1, key: "ingame-manual-ships-boa", opts:{model:"[boa]"}},
		{level: 1, key: "ingame-manual-ships-boa2", opts:{model:"[boa-mk2]"}},
		{level: 1, key: "ingame-manual-ships-cobra", opts:{model:"[cobramk1]"}},
		{level: 1, key: "ingame-manual-ships-cobra3", opts:{model:"[cobra3-trader]"}},
		{level: 1, key: "ingame-manual-ships-fdl", opts:{model:"[ferdelance]"}},
		{level: 1, key: "ingame-manual-ships-gecko", opts:{model:"[gecko]"}},
		{level: 1, key: "ingame-manual-ships-krait", opts:{model:"[krait]"}},
		{level: 1, key: "ingame-manual-ships-mamba", opts:{model:"[mamba]"}},
		{level: 1, key: "ingame-manual-ships-moray", opts:{model:"[moray]"}},
		{level: 1, key: "ingame-manual-ships-shuttle", opts:{model:"[shuttle]"}},
		{level: 1, key: "ingame-manual-ships-python", opts:{model:"[python]"}},			
		{level: 1, key: "ingame-manual-ships-sidewinder", opts:{model:"[sidewinder]"}},
		{level: 1, key: "ingame-manual-ships-transporter", opts:{model:"[transporter-miner]"}},
		{level: 1, key: "ingame-manual-ships-viper", opts:{model:"[viper]"}},
		{level: 1, key: "ingame-manual-ships-interceptor", opts:{model:"[viper-interceptor]"}},
		{level: 1, key: "ingame-manual-ships-worm", opts:{model:"[worm]"}},
		{level: 1, key: "ingame-manual-ships-thargoid", opts:{model:"[thargoid]"}},
		{level: 1, key: "ingame-manual-ships-tharglet", opts:{model:"[tharglet]"}},
		{level: 0, key: "ingame-manual-end"} // temp entry
	];

	this._registerBook("ingame-manual",expandMissionText("ingame-manual-title"),contents);

}

this._setBook = function(key) {
	if (!this.$books[key]) {
		return;
	}
	this.$contents = this.$books[key].contents;
	if (!this.$fcb) {
		this.$fcb = addFrameCallback(this._precalculateOffsets.bind(this));
	}
	
	if (this.$bookmark[key]) {
		while (this.$bookmark[key].length < 4)
		{
			this.$bookmark[key].push(0);
		}
		this.$chapter = this.$bookmark[key][0];
		this.$offset = this.$bookmark[key][1];
		this.$mfdChapter = this.$bookmark[key][2];
		this.$mfdOffset = this.$bookmark[key][3];
	}	else {
		this.$chapter = 0;
		this.$offset = 0;
		this.$mfdChapter = 0;
		this.$mfdOffset = 0;
		this.$bookmark[key] = [0,0,0,0];
	}
	this.$currentBook = key;
	
	// reset MFD if active
	this.$mfdOn = false;
	this.$mfdMode = 0;
	this._hideMFD();

}

this._getBookListAlpha = function() {
	var keys = Object.keys(this.$books);
	keys.sort(function(a,b) {
		return this.$books[a].title.localeCompare(this.$books[b].title);
	}.bind(this));
	return keys;
}

/** Mission screen management **/

this._showLibrary = function() {
	this.$mode = "firstrun";
	// init and enter loop
	this._showManual();
}

this._showManual = function() {
	this.$hudHidden = player.ship.hudHidden;
	this.$manualActive = true;
	player.ship.hudHidden = true;
	if (this.$mode == "firstrun") {
		if (Object.keys(this.$books).length > 1) {
			this.$mode = "book";
			this.$offset = 0;
		} else {
			this.$mode = "read";
		}
	}
	if (this.$mode == "contents") {
		this._showChapterList();
	} else if (this.$mode == "read") {
		this._showPage();
	} else if (this.$mode == "book") {
		this._showBookList();
	}
}

this.guiScreenChanged = function(to, from) {
	if (from == "GUI_SCREEN_MISSION" && this.$manualActive) {
		this.$manualActive = false;
		player.ship.hudHidden = this.$hudHidden;
		if (this.$fcbM) {
			removeFrameCallback(this.$fcbM);
			delete this.$fcbM;
		}
	}
}

this._showBookList = function() {
	var blank = {unselectable:true, text:""};
	var listchoices = new Object;
	if (this.$offset >= this.$chaptersperpage) {
		listchoices["51_BOOK_BACK"] = {
			text:expandMissionText("ships-library-contents-back"),
			alignment:"LEFT",
			color:"orangeColor"
		}
	} else {
		listchoices["51_BOOK_BACK"] = blank;
	}
	listchoices["53_SPACER"] = blank;
	var books = this._getBookListAlpha();
	var start = this.$offset-(this.$offset%this.$chaptersperpage);
	for (var i=start; i<start+this.$chaptersperpage; i++) {
		var istr = String(i);
		if (i < 10) {
			istr = "0"+String(i);
		}
		if (books.length > i) {
			listchoices["55_BOOK_SELECT_"+istr+"_"+books[i]] = {
				text:this.$books[books[i]].title,
				alignment:"LEFT"
			}
		} else {
			listchoices["55_BOOK_SELECT_"+istr+"_"] = blank;
		}
	}
	listchoices["57_SPACER"] = blank;
	if (start+this.$chaptersperpage < books.length) {
		listchoices["59_BOOK_FORWARD"] = {
			text:expandMissionText("ships-library-contents-forward"),
			alignment:"RIGHT",
			color:"orangeColor"
		}
	} else {
		listchoices["59_CONTENTS_FORWARD"] = blank;
	}
	for (var i=24;i<26;i++) {
		listchoices["98_SPACER"+i] = blank;
	}
	//		listchoices["50_BOOK"] = expandMissionText("ships-library-changebook");
	listchoices["99_EXIT"] = expandMissionText("ships-library-exit");

	mission.runScreen({
		screenID: "ships-library",
		titleKey: "ships-library-bookselect",
		allowInterrupt: true,
		exitScreen: "GUI_SCREEN_INTERFACES",
		choices: listchoices,
		initialChoicesKey: this.$lastchoice?this.$lastchoice:"55_CONTENTS_SELECT_"+this.$offset
	},this._manualHandler,this);

}

this._showChapterList = function() {
	// 0 = back in list (?)
	// 2-11 = chapter headings (read chapter)
	// 13 = forward in list (?)
	// 20 = exit
	var blank = {unselectable:true, text:""};
	var listchoices = new Object;
	if (this.$chapter >= this.$chaptersperpage) {
		listchoices["11_CONTENTS_BACK"] = {
			text:expandMissionText("ships-library-contents-back"),
			alignment:"LEFT",
			color:"orangeColor"
		}
	} else {
		listchoices["11_CONTENTS_BACK"] = blank;
	}
	listchoices["13_SPACER"] = blank;
	var start = this.$chapter-(this.$chapter%this.$chaptersperpage);
	for (var i=start; i<start+this.$chaptersperpage; i++) {
		var istr = String(i);
		if (i < 10) {
			istr = "0"+String(i);
		}
		if (this.$contents.length > i) {
			var indent = "";
			if (this.$contents[i].level > 0) {
				indent += Array(this.$contents[i].level+1).join("... ");
			}
			listchoices["15_CONTENTS_SELECT_"+istr] = {
				text:indent+expandMissionText(this.$contents[i].key+"-title"),
				alignment:"LEFT"
			}
		} else {
			listchoices["15_CONTENTS_SELECT_"+istr] = blank;
		}
	}
	listchoices["17_SPACER"] = blank;
	if (start+this.$chaptersperpage < this.$contents.length) {
		listchoices["19_CONTENTS_FORWARD"] = {
			text:expandMissionText("ships-library-contents-forward"),
			alignment:"RIGHT",
			color:"orangeColor"
		}
	} else {
		listchoices["19_CONTENTS_FORWARD"] = blank;
	}
	for (var i=24;i<25;i++) {
		listchoices["48_SPACER"+i] = blank;
	}
	listchoices["50_BOOK"] = expandMissionText("ships-library-changebook");
	listchoices["99_EXIT"] = expandMissionText("ships-library-exit");

	mission.runScreen({
		screenID: "ships-library",
		titleKey: "ships-library-chapterselect",
		allowInterrupt: true,
		exitScreen: "GUI_SCREEN_INTERFACES",
		choices: listchoices,
		initialChoicesKey: this.$lastchoice?this.$lastchoice:"15_CONTENTS_SELECT_"+this.$chapter
	},this._manualHandler,this);

}


this._showPage = function() {
	var chapter = this.$contents[this.$chapter];

	var text = this._textFromOffset(this.$chapter,this.$offset,this.$msStore,this.$msRows,this.$msCols);
	
	var opts = {
		screenID: "ships-library",
		titleKey: chapter.key+"-title",
		allowInterrupt: true,
		exitScreen: "GUI_SCREEN_INTERFACES",
		choices: {
			"01_PREV":expandMissionText("ships-library-page-back"),
			"09_NEXT":expandMissionText("ships-library-page-next"),
			"10_CONTENTS":expandMissionText("ships-library-contents"),
			"99_EXIT":expandMissionText("ships-library-exit"),
		},
		initialChoicesKey: this.$lastchoice?this.$lastchoice:"09_NEXT",
		message: text
	};
	if (chapter.opts) {
		var extras = Object.keys(chapter.opts)
		for (var k=0;k<extras.length;k++) {
			opts[extras[k]] = chapter.opts[extras[k]];
		}
	}
	if (chapter.backgrounds) {
		var page = this._pageOfOffset(chapter,this.$offset,this.$msStore);
		if (page < chapter.backgrounds.length) {
			var backg = chapter.backgrounds[page];
		} else {
			var backg = chapter.backgrounds[chapter.backgrounds.length-1];
		}
		if (backg != "") {
			opts.overlay = backg;
		}
	}

	mission.runScreen(opts,this._manualHandler,this);
	this.$fcbM = addFrameCallback(this._moveShip.bind(this));

}

this._moveShip = function(delta) {
	if (mission.displayModel) {
		if (mission.displayModel.position.y >= -1) {
			mission.displayModel.position = new Vector3D(0,-mission.displayModel.position.z/2.5,mission.displayModel.position.z*2.5);
		}
	}
}

this._setBookmark = function() {
	this.$bookmark[this.$currentBook][0] = this.$chapter;		
	this.$bookmark[this.$currentBook][1] = this.$offset;		
	this.$bookmark[this.$currentBook][2] = this.$mfdChapter;		
	this.$bookmark[this.$currentBook][3] = this.$mfdOffset;		
}

this._manualHandler = function(choice) {
	//		log(this.name,choice);
	delete this.$lastchoice;
	if (!choice) {
		return; // launched while reading?
	} else if (choice == "01_PREV") {
		if (this.$offset == 0) {
			if (this.$chapter == 0) {
				this.$mode = "contents";
			} else {
				this.$mode = "read";
				this.$chapter--;
				this.$offset = this._lastPageOffset(this.$chapter,this.$msStore);
				this._setBookmark();
			}
		} else {
			this.$mode = "read";
			this.$offset = this._previousOffset(this.$chapter,this.$offset,this.$msStore,this.$msRows,this.$msCols);
			this._setBookmark();
		}
		this.$lastchoice = choice;
	} else if (choice == "05_READ") {
		this.$mode = "read";
	} else if (choice == "09_NEXT") {
		this.$offset = this._nextOffset(this.$chapter,this.$offset,this.$msStore,this.$msRows,this.$msCols);
		if (this.$offset == -1) {
			this.$offset = 0;
			this.$chapter++;
			if (this.$chapter >= this.$contents.length) {
				this.$mode = "contents";
				this.$chapter = 0;
			} else {
				this._setBookmark();
				this.$mode = "read";
			}
		} else {
			this._setBookmark();
		}
	} else if (choice == "10_CONTENTS") {
		this.$mode = "contents";
		this.$offset = 0;
	} else if (choice == "11_CONTENTS_BACK") {
		this.$mode = "contents";
		this.$offset = 0;
		this.$chapter -= 1+(this.$chapter%this.$chaptersperpage);
		this.$lastchoice = choice;
	} else if (choice.match(/^15_CONTENTS_SELECT_/)) {
		this.$chapter = parseInt(choice.slice(19),10);
		this.$offset = 0;
		this._setBookmark();
		this.$mode = "read";
	} else if (choice == "19_CONTENTS_FORWARD") {
		this.$mode = "contents";
		this.$offset = 0;
		this.$chapter += this.$chaptersperpage-(this.$chapter%this.$chaptersperpage);
		this.$lastchoice = choice;
	} else if (choice == "50_BOOK") {
		this.$mode = "book";
		this.$offset = 0;
	} else if (choice == "51_BOOK_BACK") {
		this.$offset -= this.$chaptersperpage;
	} else if (choice == "59_BOOK_FORWARD") {
		this.$offset += this.$chaptersperpage;
	} else if (choice.match(/^55_BOOK_SELECT_/)) {
		this._setBook(choice.slice(18));
		this.$mode = "read";
	}

	// 01_PREV: reduce offset to prev page, or last page of prev chapter
	// 05_READ: switch to read mode at current chapter + offset
	// 09_NEXT: advance offset to next page, or next chapter if no more
	// 10_CONTENTS: switch to contents mode at current chapter, zero offset
	// 11_CONTENTS_BACK: up one page in chapter list
	// 15_CONTENTS_SELECT_[0-9]: switch to read mode in chapter at offset
	// 19_CONTENTS_FORWARD: down one page in chapter list
	// 99_EXIT: quit


	if (choice != "99_EXIT") {
		this._showManual();
	}
}

/** MFD functions **/

this._equipPressMode = function()
{
	this.$mfdMode = (this.$mfdMode + 1) % 5;
	player.consoleMessage(expandDescription("[ships-library-mfd-mode][ships-library-mfd-mode"+this.$mfdMode+"]"));
}

this._equipPressActivate = function()
{
	if (player.ship.multiFunctionDisplays == 0)
	{
		player.consoleMessage(expandDescription("[ships-library-mfd-unavailable]"));
		return;
	}
	if (this.$mfdMode == 0) // power
	{
		this.$mfdOn = !this.$mfdOn;
		if (this.$mfdOn)
		{
			// out of range value picks first free MFD, if there is one
			player.ship.setMultiFunctionDisplay(player.ship.multiFunctionDisplays,"ships-library")
			player.consoleMessage(expandDescription("[ships-library-mfd-on]"));
		}
	}
	else if (this.$mfdMode == 1) // next page
	{
		this.$mfdOffset = this._nextOffset(this.$mfdChapter,this.$mfdOffset,this.$mfdStore,this.$mfdRows,this.$mfdCols);
		if (this.$mfdOffset == -1) {
			this.$mfdOffset = 0;
			this.$mfdChapter++;
			if (this.$mfdChapter >= this.$contents.length) {
				this.$mfdChapter = 0;
			} else {
				this._setBookmark();
			}
		} else {
			this._setBookmark();
		}
	}
	else if (this.$mfdMode == 2) // prev page
	{
		if (this.$mfdOffset == 0) {
			if (this.$mfdChapter == 0) {
				this.$mfdChapter = this.$contents.length-1;
			} else {
				this.$mfdChapter--;
				this.$mfdOffset = this._lastPageOffset(this.$mfdChapter,this.$mfdStore);
				this._setBookmark();
			}
		} else {
			this.$mfdOffset = this._previousOffset(this.$mfdChapter,this.$mfdOffset,this.$mfdStore,this.$mfdRows,this.$mfdCols);
			this._setBookmark();
		}
	}
	else if (this.$mfdMode == 3) // next chapter
	{
		this.$mfdOffset = 0;
		this.$mfdChapter++;
		if (this.$mfdChapter >= this.$contents.length) {
			this.$mfdChapter = 0;
		}
	}
	else if (this.$mfdMode == 4) // next chapter
	{
		this.$mfdOffset = 0;
		this.$mfdChapter--;
		if (this.$mfdChapter < 0) {
			this.$mfdChapter = this.$contents.length-1;
		}
	}

	if (this.$mfdOn)
	{
		this._showMFD();
	}
	else
	{
		this._hideMFD();
	}
}

this._showMFD = function()
{
	var text = this._textFromOffset(this.$mfdChapter,this.$mfdOffset,this.$mfdStore,this.$mfdRows,this.$mfdCols);
	text = this._mfdBreak(text);
	player.ship.setMultiFunctionText("ships-library",text);
}

this._hideMFD = function()
{
	player.ship.setMultiFunctionText("ships-library");
}

this._mfdBreak = function(text)
{
	if (text == String.fromCharCode(30))
	{
		return "---image unavailable---";
	}
	var linewidth = this.$mfdCols;
	var lines = [];
	var paras = text.split("\n");
	
	var pl = paras.length;
	for (var i=0;i<pl;i++) {
		var accum = "";
		var words = paras[i].split(" ");
		var widthuse = 0;
		var wl = words.length;
		for (var j=0;j<wl;j++) {
			var word = (accum==""?"":" ")+words[j];
			var wordwidth = defaultFont.measureString(word);
			if (widthuse+wordwidth < linewidth) {
				accum += word;
				widthuse+=wordwidth;
			} else {
				lines.push(accum);
				accum = word.slice(1);
				widthuse = wordwidth - this.$spaceWidth;
			}
		}
		lines.push(accum);
	}
	return lines.join("\n");
}


/** Utility functions **/

this._blockFromOffset = function(chapternum,offset,lines,linewidth) {
	//		log(this.name,"Calculating block: "+key+","+offset);
	var pagebreak = String.fromCharCode(30);
//	var lines = this.$rows;
//	var linewidth = this.$cols;
	
	var fulltext = this._getText(chapternum);
	
	var text = fulltext.slice(offset,offset+(lines*linewidth*4));
	var paras = text.split("\n");
	var accum = "";
	var lineuse = 0;
	var pl = paras.length;
	for (var i=0;i<pl;i++) {
		var words = paras[i].split(" ");
		var widthuse = 0;
		var wl = words.length;
		for (var j=0;j<wl;j++) {
			if (words[j] == pagebreak) {
				lineuse = lines;
				accum += pagebreak+"\n";
				break; // page break character
			}
			var word = (j==0?"":" ")+words[j];
			var wordwidth = defaultFont.measureString(word);
			if (widthuse+wordwidth < linewidth) {
				accum += word;
				widthuse+=wordwidth;
			} else {
				widthuse=0;
				lineuse++;
				if (lineuse >= lines) {
					accum += "\n";
					break;
				} else {
					accum += word;
					if (j==0)
					{
						widthuse+=wordwidth;
					}
					else
					{
						widthuse+=wordwidth - this.$spaceWidth;
					}
				}
			}
		}
		if (lineuse >= lines) {
			break;
		}
		lineuse++;
		accum += "\n";
		if (lineuse >= lines) {
			break;
		}
	}

	if (lineuse < this.$minblock && lines == this.$msRows) {
		this.$short = true;
	} else {
		this.$short = false;
	}
	//		log(this.name,"Calculated block: "+key+","+offset);
	return accum;
}

// offset calculation can be fairly slow for large text blocks
// this calculates it in the background a page at a time
// while the player is docked and frame rate is less crucial
this._precalculateOffsets = function() {
	if (!player.ship || !player.ship.docked) {
		return;
	}
	for (var i=0;i<this.$contents.length;i++) {
		if (this._precalculateOffset(i)) {
			return;
		}
	}
	removeFrameCallback(this.$fcb);
	delete this.$fcb;
}

this._precalculateOffset = function(chapternum) {
	var chapter = this.$contents[chapternum];
	if (chapter.offsets && chapter.miniOffsets) {
		return false;
	}
	if (!chapter._offsets) {
		chapter._offsets = [0];
	}
	if (!chapter._miniOffsets) {
		chapter._miniOffsets = [0];
	}

	var fulltext = this._getText(chapternum);
	var offset = chapter._offsets[chapter._offsets.length-1];
	var block = this._blockFromOffset(chapternum,offset,this.$msRows,this.$msCols);
	offset += block.length;
	if (offset < fulltext.length) {
		// add next block
		chapter._offsets.push(offset);
	} else {
		// done
		if (this.$short && chapter._offsets.length > 1) {
			chapter._offsets.pop(); // merge last block into previous
		}
		chapter._offsets.push(-1);
		chapter.offsets = chapter._offsets;
		delete chapter._offsets;
	}

	offset = chapter._miniOffsets[chapter._miniOffsets.length-1];
	block = this._blockFromOffset(chapternum,offset,this.$mfdRows,this.$mfdCols);
	offset += block.length;
	if (offset < fulltext.length) {
		// add next block
		chapter._miniOffsets.push(offset);
	} else {
		// done
		if (this.$short && chapter._miniOffsets.length > 1) {
			chapter._miniOffsets.pop(); // merge last block into previous
		}
		chapter._miniOffsets.push(-1);
		chapter.miniOffsets = chapter._miniOffsets;
		delete chapter._miniOffsets;
	}


	return true;
}


this._calculateOffsets = function(chapternum,rows,cols) {
	var offsets = new Array;
	var fulltext = this._getText(chapternum);

	var offset = 0;
	this.$short = false;
	while (offset < fulltext.length && !this.$short) {
		offsets.push(offset);
		var block = this._blockFromOffset(chapternum,offset,rows,cols);
		offset += block.length;
	}
	if (this.$short && offsets.length > 1) {
		offsets.pop(); // merge last block into previous
	}

	offsets.push(-1);

	return offsets;
}


this._textFromOffset = function(chapternum,fromoffset,otype,rows,cols) {
	var fulltext = this._getText(chapternum);

	var tooffset = this._nextOffset(chapternum,fromoffset,otype,rows,cols);
	if (tooffset == -1) {
		var slice = fulltext.slice(fromoffset);
	} else {
		var slice = fulltext.slice(fromoffset,tooffset);
	}
	return slice.trim();
}

this._getText = function(chapternum) {
	var chapter = this.$contents[chapternum];
	var params = new Object;
	params["ships-library-page-break"] = String.fromCharCode(30)+"\n";
	if (!chapter.params) {
		return expandMissionText(chapter.key+"-text",params);
	} else {
		for (var i=0;i<chapter.params.length;i++) {
			params["ships_library_param_"+i] = chapter.params[i]();
		}
		return expandMissionText(chapter.key+"-text",params);
	}
}

this._pageOfOffset = function(chapter,offset,otype) {
	// only call this function when chapter.offsets must have been
	// calculated already

	for (var i=0;i<chapter[otype].length;i++) {
		if (offset <= chapter[otype][i]) {
			return i;
		}
	}
	return chapter[otype].length;
}

this._nextOffset = function(chapternum,fromoffset,otype,rows,cols) {
	var chapter = this.$contents[chapternum];
	if (!chapter[otype]) {
		chapter[otype] = this._calculateOffsets(chapternum,rows,cols);
		delete chapter._offsets;
	}

	for (var i=0;i<chapter[otype].length;i++) {
		if (fromoffset <= chapter[otype][i]) {
			if (i==chapter[otype].length-1) {
				return -1;
			}
			return chapter[otype][i+1];
		}
	}
	return -1;
}

this._previousOffset = function(chapternum,fromoffset,otype,rows,cols) {
	var offcount = 0;
	do {
		var next = this._nextOffset(chapternum,offcount,otype,rows,cols);
		if (next < fromoffset) {
			offcount = next;
		}
	} while (next < fromoffset);
	return offcount;
}

this._lastPageOffset = function(chapternum,otype,rows,cols) {
	var offcount = 0;
	do {
		var next = this._nextOffset(chapternum,offcount,otype,rows,cols);
		if (next != -1) {
			offcount = next;
		}
	} while (next != -1);
	return offcount;
}

this._getTip = function() {
	var numtips = 12;


	var tipn = (clock.days-2084004) % numtips;
	if (tipn <= 9) {
		var tips = "00"+tipn.toString();
	} else if (tipn <= 99) {
		var tips = "0"+tipn.toString();
	} else {
		var tips = tipn.toString();
	}
	return expandMissionText("ingame-manual-tip-"+tips);
}
Scripts/ships_library_mfd.js
"use strict";
this.author      = "cim"; 
this.copyright   = "� 2011-2014 cim."; 
this.licence = "CC-BY-SA 3.0";
this.version     = "0.8"; 
this.name = "Ships Library equipment script";

this.activated = function() {
		worldScripts["Ships Library"]._equipPressActivate.bind(worldScripts["Ships Library"])();
}

this.mode = function() {
		worldScripts["Ships Library"]._equipPressMode.bind(worldScripts["Ships Library"])();
}