Scripts/interface-reordering-interfaceLib.js |
"use strict";
this.name = "InterfaceReordering_InterfaceLib";
this.author = "Alnivel";
this.copyright = "2022 Alnivel";
this.version = "0.4";
this.licence = "CC BY-NC-SA 4.0";
(function() {
let ILib = this;
this.__BaseController = function __BaseController() {}
this.__BaseController.prototype.onRun = function onRun(callback) {
this.callbackOnRun = callback;
return this;
}
this.__BaseController.prototype.onUpdate = function onUpdate(callback) {
this.callbackOnUpdate = callback;
return this;
}
this.__BasePagedController = function __BasePagedController(screenParameters) {
__BasePagedController_init(screenParameters);
}
this.__BasePagedController.prototype = Object.create(__BaseController.prototype);
this.__BasePagedController.prototype.__BasePagedController_init = function (screenParameters) {
this.screenParameters = screenParameters;
this.headerLines = [];
this.contentLines = [];
this.footerLines = [];
this.headerPrefix = "00HEADER";
this.contentPrefix = "20CONTENT";
this.footerPrefix = "40FOOTER";
this.currentPage = 0;
this.pages = 0;
this.selectedChoicesKey = undefined;
}
this.__BasePagedController.prototype.addToHeader = function addToHeader(line) {
const headerLinesCount = this.headerLines.length;
const key = (headerLinesCount < 10 ? ("0" + headerLinesCount) : headerLinesCount) + "_NO_KEY";
this.headerLines.push({ key: key, line: line });
}
this.__BasePagedController.prototype.addToContent = function addToContent(line) {
const contentLinesCount = this.contentLines.length;
const key = (contentLinesCount < 10 ? ("0" + contentLinesCount) : contentLinesCount) + "_NO_KEY";
this.contentLines.push({ key: key, line: line });
}
this.__BasePagedController.prototype.addToFooter = function addToFooter(line) {
const footerLinesCount = this.footerLines.length;
const key = (footerLinesCount < 10 ? ("0" + footerLinesCount) : footerLinesCount) + "_NO_KEY";
this.footerLines.push({ key: key, line: line });
}
/*
Remove line from ...
this.__BasePagedScreenController.prototype.removeLineFromContent = function removeFromContent(line) {
let removePos = -1;
let i = this.contentLines.length;
while (i--) {
if (this.contentLines[i].line === line) {
removePos = i;
break;
}
}
if (removePos === -1)
return false;
else {
this.contentLines.splice(removePos, 1);
return true;
}
}
*/
this.__BasePagedController.prototype.clearHeader = function clearHeader() {
this.headerLines.length = 0;
}
this.__BasePagedController.prototype.clearContent = function clearHeader() {
this.contentLines.length = 0;
}
this.__BasePagedController.prototype.clearFooter = function clearHeader() {
this.footerLines.length = 0;
}
/***
* Update every line in header and fill "choises" parameter with them
* @param {object} choises
* @returns {number} Header height in lines
*/
this.__BasePagedController.prototype.updateHeader = function updateHeader(choises) {
const linesForHeader = this.headerLines.length;
for (let i = 0; i < linesForHeader; i++) {
let pair = this.headerLines[i];
let key = this.headerPrefix + (i < 10 ? ("0" + i) : i) + "_" + pair.key;
choises[key] = pair.line.update();
}
return linesForHeader;
}
/**
* Update content lines on specified page and fill "choises" parameter with them
* @param {object} choises
* @param {number} linesPerPage Maximum number of lines per page
* @param {number} page Page number
* @returns {number} Content height on specified page in lines
*/
this.__BasePagedController.prototype.updateContent = function updateContent(choises, linesPerPage, page) {
let linesStartIndex = linesPerPage * page;
let linesEndIndex = linesStartIndex + linesPerPage;
let contentLinesCount = this.contentLines.length;
let linesEndIndexLimited = contentLinesCount < linesEndIndex ? contentLinesCount : linesEndIndex;
for (let i = linesStartIndex; i < linesEndIndexLimited; i++) {
let pair = this.contentLines[i];
let key = this.contentPrefix + (i < 10 ? ("0" + i) : i) + "_" + pair.key;
choises[key] = pair.line.update();
}
return linesEndIndexLimited - linesStartIndex;
}
/***
* Update every line in footer and fill "choises" parameter with them
* @param {object} choises
* @returns {number} Footer height in lines
*/
this.__BasePagedController.prototype.updateFooter = function updateFooter(choises) {
const linesForFooter = this.footerLines.length;
for (let i = 0; i < linesForFooter; i++) {
let pair = this.footerLines[i];
let key = this.footerPrefix + (i < 10 ? ("0" + i) : i) + "_" + pair.key;
choises[key] = pair.line.update();
}
return linesForFooter;
}
this.__BasePagedController.prototype.proccessChoises = function proccessChoises(choise) {
for (let i = 0; i < this.headerLines.length; i++) {
let pair = this.headerLines[i];
let key = this.headerPrefix + (i < 10 ? ("0" + i) : i) + "_" + pair.key;
let line = pair.line;
if (choise === key) {
return line.select && line.select(); // if true returned then don`t redraw this screen
}
}
for (let i = 0; i < this.contentLines.length; i++) {
let pair = this.contentLines[i];
let key = this.contentPrefix + (i < 10 ? ("0" + i) : i) + "_" + pair.key;
let line = pair.line;
if (choise === key) {
return line.select && line.select();
}
}
for (let i = 0; i < this.footerLines.length; i++) {
let pair = this.footerLines[i];
let key = this.footerPrefix + (i < 10 ? ("0" + i) : i) + "_" + pair.key;
let line = pair.line;
if (choise === key) {
return line.select && line.select();
}
}
// Restarting the screen, if necessary, is performed by the child
/*
this.screenParameters.choices = this.getPage(this.currentPage);
this.screenParameters.initialChoicesKey = initialChoicesKey;
mission.runScreen(this.screenParameters, this.onChoiceSelected.bind(this));
*/
}
this.__BasePagedController.prototype.runScreen = function runScreen() {
log("Alnivel_InterfaceLib_Message", "Started " + this.screenParameters.title + " mission screen");
if(this.callbackOnRun)
this.callbackOnRun();
this.currentPage = 0;
this.screenParameters.choices = this.getPage(0);
mission.runScreen(this.screenParameters, this.onChoiceSelected.bind(this));
}
this.__BasePagedController.prototype.returnToScreen = function returnToScreen() {
if(this.currentPage === undefined || this.currentPage > this.pages)
this.currentPage = 0;
log("Alnivel_InterfaceLib_Message", "Returned to " + this.screenParameters.title + " mission screen on page " + this.currentPage);
if(this.callbackOnRun)
this.callbackOnRun();
this.screenParameters.choices = this.getPage(this.currentPage);
this.screenParameters.initialChoicesKey = this.selectedChoicesKey;
mission.runScreen(this.screenParameters, this.onChoiceSelected.bind(this));
}
/**
* Create paged screen that can be filled with line objects.
* Each time the line is selected, it will re-render,
* unless the Return option is selected (then will be called callbackOnReturn)
* or the onSelect callback of the selected line returns true
* @param {*} screenParameters
* @param {Function} [callbackOnReturn]
*/
this.PagedScreenController = function PagedScreenController(screenParameters, callbackOnReturn) {
this.__BasePagedController_init(screenParameters); // Don`t know how to get base constructor properly
this.callbackOnReturn = callbackOnReturn ? callbackOnReturn : null;
}
this.PagedScreenController.prototype = Object.create(__BasePagedController.prototype);
this.PagedScreenController.prototype.onReturn = function onReturn(callback) {
this.callbackOnReturn = callback;
return this;
}
this.PagedScreenController.prototype.updateControls = function updateControls(choises, pages, page) {
const spacer = { text: "" };
if (pages > 1) {
choises["70_SPACER"] = spacer;
choises["71_PAGE_NUM"] = {
text: "Page " + (1 + page) + " of " + pages,
unselectable: true
}
choises["73_NEXT_PAGE"] = {
text: "Next page",
unselectable: page + 1 === pages
};
choises["74_PREV_PAGE"] = {
text: "Previous page",
unselectable: page === 0
};
} else {
choises["70_SPACER"] = spacer;
choises["71_SPACER"] = spacer;
choises["73_SPACER"] = spacer;
choises["74_SPACER"] = spacer;
}
choises["80_RETURN"] = { text: "Return" };
return 5;
}
this.PagedScreenController.prototype.getPage = function getPage(page) {
if(this.callbackOnUpdate)
this.callbackOnUpdate();
const screenHeight = (player.ship.hudAllowsBigGui ? 27 : 21);
const choises = {};
const headerHeight = this.updateHeader(choises);
const footerHeight = this.updateFooter(choises);
const controlsHeight = 5;
const maxContentHeight = screenHeight - headerHeight - footerHeight - controlsHeight;
const pages = this.pages = Math.ceil(this.contentLines.length / maxContentHeight);
const contentHeight = this.updateContent(choises, maxContentHeight, page);
const remainHeight = maxContentHeight - contentHeight;
log("Alnivel_InterfaceLib_Message", "Remain lines after content " + remainHeight + " from " + maxContentHeight)
/*** Fill empty ***/
const spaceToFill = remainHeight < maxContentHeight? remainHeight : maxContentHeight;
const spacer = { text: "" };
for (let i = 0; i < spaceToFill; i++) {
let key = "21EMPTY_" + (i < 10 ? ("0" + i) : i) + "_EMPTY";
choises[key] = spacer;
}
/*** Controlls ***/
this.updateControls(choises, pages, page);
return choises;
}
this.PagedScreenController.prototype.getPageWithLineIndex = function getPageWithLineIndex(lineIndex) {
if(lineIndex === null || lineIndex === undefined ||
lineIndex === -1 || lineIndex >= this.contentLines.length ) {
// TODO: add warning
return this.getPage(0);
}
const screenHeight = (player.ship.hudAllowsBigGui ? 27 : 21);
const choises = {};
const headerHeight = this.updateHeader(choises);
const footerHeight = this.updateFooter(choises);
const controlsHeight = 5;
const maxContentHeight = screenHeight - headerHeight - footerHeight - controlsHeight;
const pages = this.pages = Math.ceil(this.contentLines.length / maxContentHeight);
const page = this.currentPage = Math.floor(lineIndex / maxContentHeight);
const contentHeight = this.updateContent(choises, maxContentHeight, page);
const remainHeight = maxContentHeight - contentHeight;
log("Alnivel_InterfaceLib_Message", "Remain lines after content " + remainHeight + " from " + maxContentHeight)
/*** Fill empty ***/
const spaceToFill = remainHeight < maxContentHeight? remainHeight : maxContentHeight;
const spacer = { text: "" };
for (let i = 0; i < spaceToFill; i++) {
let key = "21EMPTY_" + (i < 10 ? ("0" + i) : i) + "_EMPTY";
choises[key] = spacer;
}
/*** Controlls ***/
this.updateControls(choises, pages, page);
return choises;
}
this.PagedScreenController.prototype.getPageWithLine = function getPageWithLine(line) {
const lineIndex = this.contentLines.findIndex(function (pair) {
return pair.line === line;
});
return this.getPageWithLineIndex(lineIndex);
}
this.PagedScreenController.prototype.onChoiceSelected = function onChoiceSelected(choise) {
if (choise === "80_RETURN") {
if (this.callbackOnReturn) this.callbackOnReturn();
return;
}
this.selectedChoicesKey = choise;
if (choise === "74_PREV_PAGE") {
this.currentPage--;
if (this.currentPage === 0)
this.selectedChoicesKey = "73_NEXT_PAGE";
}
else if (choise === "73_NEXT_PAGE") {
this.currentPage++;
if (this.currentPage + 1 === this.pages)
this.selectedChoicesKey = "74_PREV_PAGE";
}
else {
const stopRestartingScreen = this.proccessChoises(choise);
if(stopRestartingScreen)
{
log("Alnivel_InterfaceLib_Message", "Screen \"" + this.screenParameters.title + "\" restarting has been stopped by key " + choise)
return;
}
}
this.screenParameters.choices = this.getPage(this.currentPage);
this.screenParameters.initialChoicesKey = this.selectedChoicesKey;
mission.runScreen(this.screenParameters, this.onChoiceSelected.bind(this));
}
this.PagedScreenController.prototype.runPage = function runPage(page, noUpdate) {
log("Alnivel_InterfaceLib_Message", "Started " + this.screenParameters.title + " mission screen");
if(!noUpdate && this.callbackOnRun)
this.callbackOnRun();
else
this.screenParameters.initialChoicesKey = this.selectedChoicesKey;
if(this.currentPage === undefined || this.currentPage > this.pages)
this.currentPage = 0;
else
this.currentPage = page;
this.screenParameters.choices = this.getPage(this.currentPage);
mission.runScreen(this.screenParameters, this.onChoiceSelected.bind(this));
}
this.PagedScreenController.prototype.runPageWithLine = function runPageWithLine(line, noUpdate) {
log("Alnivel_InterfaceLib_Message", "Started " + this.screenParameters.title + " mission screen");
if(!noUpdate && this.callbackOnRun)
this.callbackOnRun();
else
this.screenParameters.initialChoicesKey = this.selectedChoicesKey;
this.currentPage = 0;
this.screenParameters.choices = this.getPageWithLine(line);
mission.runScreen(this.screenParameters, this.onChoiceSelected.bind(this));
}
this.PagedScreenController.prototype.runPageWithLineIndex = function runPageWithLineIndex(lineIndex, noUpdate) {
log("Alnivel_InterfaceLib_Message", "Started " + this.screenParameters.title + " mission screen");
if(!noUpdate && this.callbackOnRun)
this.callbackOnRun();
else
this.screenParameters.initialChoicesKey = this.selectedChoicesKey;
this.currentPage = 0;
this.screenParameters.choices = this.getPageWithLineIndex(lineIndex);
mission.runScreen(this.screenParameters, this.onChoiceSelected.bind(this));
}
this.$limitText = function $limitText(text, limitWidth) {
const ellipsis = "…";
const hairSpace = String.fromCharCode(31);
const hairSpaceWidth = defaultFont.measureString(hairSpace);
let tmp = text;
while (defaultFont.measureString(tmp) > limitWidth) {
tmp = tmp.substring(0, tmp.length - 2) + ellipsis;
}
const padsNeeded = Math.floor((limitWidth - defaultFont.measureString(tmp)) / hairSpaceWidth);
if (padsNeeded > 1)
tmp = tmp + new Array(padsNeeded).join(hairSpace);
return tmp;
}
/**
*
* @param {string} text
* @param {"left"|"right"} padSide
* @param {number} desiredWidth
* @returns Padded text
*/
this.$padTextOnSide = function $padTextOnSide(text, padSide, desiredWidth) {
const hairSpace = String.fromCharCode(31);
const hairSpaceWidth = defaultFont.measureString(hairSpace);
const textWidth = defaultFont.measureString(text);
const padsNeeded = Math.floor((desiredWidth - textWidth) / hairSpaceWidth);
let resultText;
if (padsNeeded > 1)
if (padSide == "left")
resultText = new Array(padsNeeded).join(hairSpace) + text;
else
resultText = text + new Array(padsNeeded).join(hairSpace);
else
resultText = this.$limitText(text, desiredWidth);
return resultText;
}
this.$padTextAround = function $padTextAround(text, desiredWidth) {
const hairSpace = String.fromCharCode(31);
const hairSpaceWidth = defaultFont.measureString(hairSpace);
const textWidth = defaultFont.measureString(text);
const padsNeeded = Math.floor((desiredWidth - textWidth) / (2 * hairSpaceWidth));
let resultText;
if (padsNeeded > 1) {
let sidePadding = new Array(padsNeeded).join(hairSpace);
resultText = sidePadding + text + sidePadding;
}
else
resultText = this.$limitText(text, desiredWidth);
return resultText;
}
this.$repeatPattern = function $repeatPattern(pattern, desiredWidth) {
const patternWidth = defaultFont.measureString(pattern);
const repeatsNeeded = Math.ceil(desiredWidth / patternWidth);
let tmp = new Array(repeatsNeeded).join(pattern);
while (defaultFont.measureString(tmp) > desiredWidth) {
tmp = tmp.substring(0, tmp.length - 1);
}
return tmp
}
this.__LineWithCallbacksPrototype = {
/**
* Set callback that will be called when line is redrawing.
* The arguments for the function are same as for select callback,
* but it can return dictionary whose
* key-value pairs will be setted as choice parameters
* @param {Function} updateCallback
* @returns Line object
*/
onUpdate: function onUpdate(updateCallback) {
this.updateCallback = updateCallback;
return this;
},
/**
* Set callback that will be called when line is selected.
* The arguments for the function depends on line type.
* Is callback return true than screen will not redrawed.
* It can be used to change to another mission screen.
* @param {*} selectCallback
* @returns Line object
*/
onSelected: function onSelected(selectCallback) {
this.selectCallback = selectCallback;
return this;
}
}
// TODO: Add lines hiding?
/**
* Create empty line object
*/
this.EmptyLine = function () {}
this.EmptyLine.prototype.update = function update() {
return { text: "" };
}
/**
* Create line object that can`t be selected
* @param {string} text
*/
this.LabelLine = function (text, alignment) {
this.choiceObject = {
text: ILib.$padTextAround(text, 32),
unselectable: true,
color: "whiteColor",
alignment: alignment? alignment : "CENTER"
}
}
this.LabelLine.prototype = Object.create(__LineWithCallbacksPrototype);
this.LabelLine.prototype.update = function update() {
if (this.updateCallback) {
let updated = this.updateCallback();
const updatedKeys = Object.keys(updated);
for (let i = 0; i < updatedKeys.length; i++) {
const key = updatedKeys[i];
const value = updated[key];
if (value !== undefined &&
key !== "unselectable") {
if (key === "text")
this.choiceObject[key] = ILib.$padTextOnSide(value, "right", 32);
else if (key === "color")
this.choiceObject[key] = value === "default" ? "whiteColor" : value;
else
this.choiceObject[key] = value;
}
}
}
return this.choiceObject;
};
/**
* Create line object that can be selected.
* When selected onSelected callback will be called without any arguments
* @param {string} text
*/
this.ButtonLine = function (text) {
this.choiceObject = {
text: ILib.$padTextOnSide(text, "right", 32),
color: "cyanColor",
alignment: "LEFT"
};
this.selectableColor = "cyanColor";
this.unselectableColor = "darkGrayColor";
}
this.ButtonLine.prototype = Object.create(__LineWithCallbacksPrototype);
this.ButtonLine.prototype.update = function update() {
if (this.updateCallback) {
let updated = this.updateCallback();
const updatedKeys = Object.keys(updated);
for (let i = 0; i < updatedKeys.length; i++) {
const key = updatedKeys[i];
const value = updated[key];
if (value !== undefined) {
if (key === "color")
this.selectableColor = value === "default" ? "cyanColor" : value;
else if (key === "text")
this.choiceObject[key] = ILib.$padTextOnSide(value, "right", 32);
else
this.choiceObject[key] = value;
}
}
if (this.choiceObject.unselectable)
this.choiceObject.color = this.unselectableColor;
else
this.choiceObject.color = this.selectableColor;
}
return this.choiceObject;
}
this.ButtonLine.prototype.select = function select() {
if (this.selectCallback)
return this.selectCallback();
}
/**
* Create line object that toggles between true and false when selected.
* When selected onSelected callback will be called with new state as argument
* @param {string} text
* @param {Boolean} initialState
*/
this.ToggleLine = function (text, initialState) {
this.choiceObject = {
color: "cyanColor"
};
this.selectableColor = "cyanColor";
this.unselectableColor = "darkGrayColor";
this.text = text;
this.state = !!initialState;
}
this.ToggleLine.prototype = Object.create(__LineWithCallbacksPrototype);
this.ToggleLine.prototype.update = function update() {
if (this.updateCallback) {
let updated = this.updateCallback(this.state);
const updatedKeys = Object.keys(updated);
for (let i = 0; i < updatedKeys.length; i++) {
const key = updatedKeys[i];
const value = updated[key];
if (value !== undefined) {
if (key === "color")
this.selectableColor = value === "default" ? "cyanColor" : value;
else if (key === "text")
this.text = value;
else if (key === "state")
this.state = !!value;
else
this.choiceObject[key] = value;
}
}
if (this.choiceObject.unselectable)
this.choiceObject.color = this.unselectableColor;
else
this.choiceObject.color = this.selectableColor;
}
this.choiceObject.text =
ILib.$padTextOnSide(this.text, "right", 24) +
ILib.$padTextAround(" < " + (this.state ? "On" : "Off") + " > ", 8);
return this.choiceObject;
}
this.ToggleLine.prototype.select = function select() {
this.state = !this.state;
if (this.selectCallback)
return this.selectCallback(this.state);
}
/**
* Create line object that cycles between the specified values when selected.
* When selected onSelected callback will be called with new index and value as arguments
* @param {string} text
* @param {Array|Object.<string, any>} items If it is an array the values are
* the same as the labels, otherwise it is treated as a dictionary of labels to values
* @param {any} [initialItem]
*/
this.CycleLine = function (text, items, initialItem) {
const itemsIsArray = Array.isArray(items);
const itemKeys = itemsIsArray ? items : Object.keys(items);
let itemValues;
if (itemsIsArray)
itemValues = items
else { // Object.values(items);
itemValues = Array(itemKeys.length)
for (let i = 0; i < itemKeys.length; i++)
itemValues[i] = items[itemKeys[i]]
}
let initialIndex = itemKeys.indexOf(initialItem);
if (initialIndex === -1)
initialIndex = 0;
this.choiceObject = {
color: "cyanColor",
};
this.items = itemKeys;
this.values = itemValues;
this.currentIndex = initialIndex;
this.text = text;
this.selectableColor = "cyanColor";
this.unselectableColor = "darkGrayColor";
}
this.CycleLine.prototype = Object.create(__LineWithCallbacksPrototype);
this.CycleLine.prototype.update = function update() {
if (this.updateCallback) {
let updated = this.updateCallback(this.currentIndex, this.values[this.currentIndex]);
const updatedKeys = Object.keys(updated);
for (let i = 0; i < updatedKeys.length; i++) {
const key = updatedKeys[i];
const value = updated[key];
if (value !== undefined) {
if (key === "color")
this.selectableColor = value === "default" ? "cyanColor" : value;
else if (key === "text")
this.text = value;
else if (key === "currentIndex") {
this.currentIndex = value;
}
else
this.choiceObject[key] = value;
}
}
if (this.choiceObject.unselectable)
this.choiceObject.color = this.unselectableColor;
else
this.choiceObject.color = this.selectableColor;
}
const currentItem = this.items[this.currentIndex];
this.choiceObject.text =
ILib.$padTextOnSide(this.text, "right", 24) +
ILib.$padTextAround(" < " + currentItem + " > ", 8);
return this.choiceObject;
}
this.CycleLine.prototype.select = function select() {
this.currentIndex = (this.currentIndex + 1) % this.values.length;
if (this.selectCallback)
return this.selectCallback(this.currentIndex, this.values[this.currentIndex]);
}
}).call(this); |
Scripts/interface-reordering-main.js |
"use strict";
this.name = "InterfaceReordering";
this.author = "Alnivel";
this.copyright = "2022 Alnivel";
this.version = "0.2";
this.licence = "CC BY-NC-SA 4.0";
this.$interfacePrefixes = {/* key: prefix */ };
this.$idToStationMap = [/* stationId: station */];
this.$stationIdToInterfacesMap = [/* stationId: { key: interfaceDefinition }*/];
/// Replace orginal method with one which modify original interface category
/// to visualy similar string that will be placed acording to interfaceOrder
///
/// If something add interface directly in the body of the script, the set
/// interface will be above all others and can't be reordered until it will be reset
///
/// Well, it could be worse
this.$originalSetInterface = Station.prototype.setInterface;
(function substituteSetInterface() {
let idToStationMap = this.$idToStationMap;
let stationIdToInterfacesMap = this.$stationIdToInterfacesMap;
let script = this;
let originalSetInterface = this.$originalSetInterface;
Station.prototype.setInterface = function (key, interfaceDefinition) {
let stationId = idToStationMap.indexOf(this);
if (stationId === -1) {
stationId = idToStationMap.length;
idToStationMap.push(this);
stationIdToInterfacesMap[stationId] = {};
}
let seenInterfaces = stationIdToInterfacesMap[stationId];
if (interfaceDefinition !== null) {
let reorderedDefinition = {};
for (let idKey in interfaceDefinition) {
reorderedDefinition[idKey] = interfaceDefinition[idKey];
}
// // A way to get the name of the calling script
// let stack = new Error().stack;
// let callerScript = stack.substring(
// stack.indexOf("\"(\"") + 3,
// stack.indexOf("\",[object Object])")
// );
//
// interfaceDefinition.InterfaceReordering_callerScript = callerScript;
// //
// I hope someone doesn't use the same key as an existing category
let prefix = script.$interfacePrefixes[key] ||
script.$interfacePrefixes[interfaceDefinition.category] ||
script.$interfacePrefixes["InterfaceReordering_OtherPrefix"];
reorderedDefinition.category = prefix + interfaceDefinition.category;
originalSetInterface.call(this, key, reorderedDefinition);
seenInterfaces[key] = interfaceDefinition;
}
else {
originalSetInterface.call(this, key, null);
if (seenInterfaces[key])
seenInterfaces[key] = null;
}
}
}).call(this);
this.startUp = function () {
const storedInterfaceOrder = JSON.parse(missionVariables.interfaceReordering_interfaceOrder);
if (storedInterfaceOrder)
this.$interfaceOrder = storedInterfaceOrder;
const hasOtherEntry = this.$interfaceOrder.some(function (orderItem) {
return orderItem.key === "InterfaceReordering_OTHER";
})
if (!hasOtherEntry) {
this.$interfaceOrder.push({ key: "InterfaceReordering_OTHER", name: "<Other interfaces>", type: "" });
}
// copy loaded order to variable that used by user interface
this.$unsavedInterfaceOrder.length = 0;
Array.prototype.push.apply(this.$unsavedInterfaceOrder, this.$interfaceOrder);
this.$updateInterfaceOrder(this.$interfaceOrder);
}
this.interfaceInitialized = false;
this.startUpComplete = this.shipDockedWithStation = function () {
if (!this.interfaceInitialized) {
this.$initializeInterfaceScreens();
this.interfaceInitialized = true;
}
const interfaceMain = this.$screens.main;
const station = player.ship.dockedStation;
station.setInterface("InterfaceReordering_ManageOrder", {
title: "Reorder interfaces",
category: "Ship Systems",
summary: "Change the order of interfaces",
callback: interfaceMain.runScreen.bind(interfaceMain)
});
}
this.playerWillSaveGame = function () {
missionVariables.interfaceReordering_interfacePrefixes = JSON.stringify(this.$interfacePrefixes);
missionVariables.interfaceReordering_interfaceOrder = JSON.stringify(this.$interfaceOrder);
};
// Clear known stations and interfaces before
// any interfaces from new system will be set
this.shipWillEnterWitchspace = function () {
this.$idToStationMap.length = 0;
this.$stationIdToInterfacesMap.length = 0;
};
// Settings interface //
this.$screens = {};
this.$interfaceOrder = [
{ key: "InterfaceReordering_OTHER", name: "<Other interfaces>", type: "" },
/* Other entries looks like
{key: "<interfaceKey>", name:"<interfaceName>", type:"Interface"},
{key: "<categoryName>", name:"<categoryName>", type:"Category"},
*/
];
this.$unsavedInterfaceOrder = []; // changes in order stored here until they saved
this.$initializeInterfaceScreens = function $initializeInterfaceScreens() {
/*** Binding script variables to function scope ***/
const ILib = worldScripts["InterfaceReordering_InterfaceLib"];
const mainScript = this;
const screens = this.$screens;
const stationIdToInterfacesMap = this.$stationIdToInterfacesMap;
const idToStationMap = this.$idToStationMap;
const updateInterfaceOrder = this.$updateInterfaceOrder.bind(this);
const interfaceOrder = this.$unsavedInterfaceOrder;
let selectedOrderItemKey = null;
let selectedOrderItemIndex = null;
const station = player.ship.dockedStation
const stationId = idToStationMap.indexOf(station);
const interfacesOnStation = stationId === -1 ? {} : stationIdToInterfacesMap[stationId];
/*** ***/
let interfaceOrderWasChanged = false;
const saveChangesAndResetSelected = function () {
selectedOrderItemKey = null;
selectedOrderItemIndex = null;
if (interfaceOrderWasChanged) {
updateInterfaceOrder(interfaceOrder);
interfaceOrderWasChanged = false;
}
};
const cancelChangesAndResetSelected = function () {
selectedOrderItemKey = null;
selectedOrderItemIndex = null;
if (interfaceOrderWasChanged) {
interfaceOrder.length = 0;
Array.prototype.push.apply(interfaceOrder, mainScript.$interfaceOrder)
interfaceOrderWasChanged = false;
}
this.screenParameters.initialChoicesKey = undefined;
}
const resetOrderList = function () {
selectedOrderItemIndex = null;
selectedOrderItemKey = null;
interfaceOrder.length = 0;
interfaceOrder.push({ key: "InterfaceReordering_OTHER", name: "<Other interfaces>", type: "" });
interfaceOrderWasChanged = true;
screens.interfaceOrderList.runPageWithLineIndex(0);
};
const formatToReorderingListColumns = function (no, name, type) {
return "" +
ILib.$padTextAround(no, 4) +
ILib.$padTextOnSide(name, "right", 22) +
ILib.$padTextAround("", 1) +
ILib.$padTextOnSide(type, "right", 5)
}
const updateListItem = function () {
let item = interfaceOrder[this.data_index];
let isSelected = this.data_index === selectedOrderItemIndex;
//log("InterfaceReordering_Message", "Updated line to \"" + "Name " + item.name + " Type " + item.type + "\"");
return {
text: formatToReorderingListColumns(this.data_index + 1, item.name, item.type),
color: isSelected ? "yellowColor" : "default",
alignment: "LEFT"
}
}
const selectListItem = function () {
let item = interfaceOrder[this.data_index];
selectedOrderItemKey = item.key;
selectedOrderItemIndex = this.data_index;
screens.moveItem.screenParameters.initialChoicesKey = undefined;
screens.moveItem.runPageWithLineIndex(this.data_index);
return true;
}
const spacerLine = new ILib.EmptyLine();
const delimiterLine = new ILib.LabelLine(ILib.$repeatPattern("-", 32));
const interfaceOrderListHeaderLine = new ILib.LabelLine(formatToReorderingListColumns("#", "Name", "Type"));
const interfaceOrderListScreenParameters = {
title: "Reorder interfaces",
screenID: "InterfaceReordering_Main",
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES"
}
const interfaceOrderListScreen = new ILib.PagedScreenController(interfaceOrderListScreenParameters);
interfaceOrderListScreen: {
this.$screens.main = interfaceOrderListScreen;
this.$screens.interfaceOrderList = interfaceOrderListScreen;
interfaceOrderListScreen.addToHeader(interfaceOrderListHeaderLine);
interfaceOrderListScreen.addToHeader(delimiterLine);
interfaceOrderListScreen.addToFooter(delimiterLine);
interfaceOrderListScreen.addToFooter(new ILib.ButtonLine("Save changes")
.onUpdate(function () {
return {
color: "yellowColor", // selectable color
unselectable: !interfaceOrderWasChanged
};
})
.onSelected(saveChangesAndResetSelected)
);
interfaceOrderListScreen.addToFooter(new ILib.ButtonLine("Add interface")
.onSelected(function () {
screens.addInterface.runScreen();
return true;
})
);
interfaceOrderListScreen.addToFooter(new ILib.ButtonLine("Add category")
.onSelected(function () {
screens.addCategory.runScreen();
return true;
})
);
interfaceOrderListScreen.addToFooter(new ILib.ButtonLine("Reset")
.onSelected(resetOrderList)
);
interfaceOrderListScreen.addToFooter(spacerLine);
const updateReorderingListScreen = function updateReorderingListScreen() {
this.clearContent(); // this === interfaceOrderListScreen;
const reorderListCount = interfaceOrder.length;
for (let i = 0; i < reorderListCount; i++) {
let line = new ILib.ButtonLine("Line")
.onUpdate(updateListItem)
.onSelected(selectListItem);
line.data_index = i;
this.addToContent(line);
}
}
interfaceOrderListScreen.onRun(updateReorderingListScreen);
interfaceOrderListScreen.onReturn(cancelChangesAndResetSelected);
}
const returnToOrderList = interfaceOrderListScreen.returnToScreen.bind(interfaceOrderListScreen);
const moveItemScreen = new ILib.PagedScreenController(interfaceOrderListScreenParameters);
moveItemScreen: {
this.$screens.moveItem = moveItemScreen;
moveItemScreen.addToHeader(interfaceOrderListHeaderLine);
moveItemScreen.addToHeader(delimiterLine);
moveItemScreen.addToFooter(delimiterLine);
moveItemScreen.addToFooter(new ILib.ButtonLine("Deselect")
.onSelected(function () {
selectedOrderItemKey = null;
selectedOrderItemIndex = null;
interfaceOrderListScreen.screenParameters.initialChoicesKey = interfaceOrderListScreen.selectedChoicesKey;
interfaceOrderListScreen.runPage(moveItemScreen.currentPage);
return true;
})
);
moveItemScreen.addToFooter(new ILib.ButtonLine("Move up")
.onUpdate(function () {
return {
unselectable: interfaceOrder.length <= 1
};
})
.onSelected(function () {
let minPosition = 0;
let newPosition = selectedOrderItemIndex - 1;
if (newPosition < minPosition)
newPosition = interfaceOrder.length - 1;
let temp = interfaceOrder[selectedOrderItemIndex];
interfaceOrder[selectedOrderItemIndex] = interfaceOrder[newPosition];
interfaceOrder[newPosition] = temp;
selectedOrderItemIndex = newPosition;
interfaceOrderWasChanged = true;
moveItemScreen.runPageWithLineIndex(selectedOrderItemIndex, true);
return true;
})
);
moveItemScreen.addToFooter(new ILib.ButtonLine("Move down")
.onUpdate(function () {
return {
unselectable: interfaceOrder.length <= 1
};
})
.onSelected(function () {
let maxPosition = interfaceOrder.length - 1;
let newPosition = selectedOrderItemIndex + 1;
if (newPosition > maxPosition)
newPosition = 0;
let temp = interfaceOrder[selectedOrderItemIndex];
interfaceOrder[selectedOrderItemIndex] = interfaceOrder[newPosition];
interfaceOrder[newPosition] = temp;
selectedOrderItemIndex = newPosition;
interfaceOrderWasChanged = true;
moveItemScreen.runPageWithLineIndex(selectedOrderItemIndex, true);
return true;
})
);
moveItemScreen.addToFooter(new ILib.ButtonLine("Remove")
.onUpdate(function () {
return {
unselectable: selectedOrderItemKey === "InterfaceReordering_OTHER"
}
})
.onSelected(function () {
if (selectedOrderItemKey !== "InterfaceReordering_OTHER") {
interfaceOrder.splice(selectedOrderItemIndex, 1);
let returnIndex = selectedOrderItemIndex === 1 ? 0 : selectedOrderItemIndex - 1;
selectedOrderItemKey = null;
selectedOrderItemIndex = null;
interfaceOrderWasChanged = true;
interfaceOrderListScreen.runPageWithLineIndex(returnIndex);
return true;
}
})
);
moveItemScreen.addToFooter(spacerLine);
const updateMoveItemScreen = function updateMoveItemScreen() {
this.clearContent(); // this === updateMoveItemScreen;
const reorderListCount = interfaceOrder.length;
for (let i = 0; i < reorderListCount; i++) {
let line = new ILib.LabelLine("Line")
.onUpdate(updateListItem);
line.data_index = i;
this.addToContent(line);
}
}
moveItemScreen.onRun(updateMoveItemScreen);
moveItemScreen.onReturn(cancelChangesAndResetSelected);
}
/*** Adding order items to main list ***/
const addItemAndReturnToList = function (pair, type) {
const newItemIndex = interfaceOrder.length;
interfaceOrder.push({ key: pair.key, name: pair.name, type: type });
interfaceOrderWasChanged = true;
screens.interfaceOrderList.screenParameters.initialChoicesKey = screens.interfaceOrderList.selectedChoicesKey;
screens.interfaceOrderList.runPageWithLineIndex(newItemIndex);
return true;
}
const addAllItemsAndReturnToList = function (items, type) {
let lastItemIndex;
for (let i = 0; i < items.length; i++) {
let pair = items[i];
lastItemIndex = interfaceOrder.length;
interfaceOrder.push({ key: pair.key, name: pair.name, type: type });
}
interfaceOrderWasChanged = true;
screens.interfaceOrderList.screenParameters.initialChoicesKey = screens.interfaceOrderList.selectedChoicesKey;
screens.interfaceOrderList.runPageWithLineIndex(lastItemIndex);
return true;
}
const updateItems = function updateItems(type, makePairFunc) {
const itemsInUse = interfaceOrder.reduce(function (accum, orderItem) {
if (orderItem.type === type)
accum[orderItem.key] = 1;
return accum;
}, {});
this.clearContent();
let noItemsToAdd = true;
const items = [];
for (let key in interfacesOnStation) {
let id = interfacesOnStation[key];
if (id === null)
continue;
const pair = makePairFunc(key, id);
if (itemsInUse[pair.key])
continue;
itemsInUse[pair.key] = 1;
let line = new ILib.ButtonLine(pair.name).onSelected(function () {
return addItemAndReturnToList(this.data_pair, this.data_type)
});
line.data_pair = pair;
line.data_type = type;
this.addToContent(line);
noItemsToAdd = false;
items.push(pair);
}
if (noItemsToAdd)
this.addToContent(new ILib.LabelLine("No items to add", "CENTER"));
this.data_items = items;
}
addInterfaceScreen: {
const addInterfaceScreen = this.$screens.addInterface = new ILib.PagedScreenController({
title: "Add interface",
screenID: "InterfaceReordering_AddInterface",
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES"
}, returnToOrderList)
.onRun(function () {
updateItems.call(this, "Interface", function (key, id) {
return { key: key, name: id.title };
});
});
addInterfaceScreen.addToHeader(delimiterLine);
addInterfaceScreen.addToFooter(delimiterLine);
addInterfaceScreen.addToFooter(new ILib.ButtonLine("Add all")
.onUpdate(function () {
let isItemsToAdd = addInterfaceScreen.data_items &&
addInterfaceScreen.data_items.length > 0;
return {
unselectable: !isItemsToAdd
};
})
.onSelected(function () {
const items = addInterfaceScreen.data_items;
return addAllItemsAndReturnToList.call(this, items, "Interface")
})
);
}
addCategoryScreen: {
const addCategoryScreen = this.$screens.addCategory = new ILib.PagedScreenController({
title: "Add category",
screenID: "InterfaceReordering_AddCategory",
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES"
}, returnToOrderList)
.onRun(function () {
updateItems.call(this, "Category", function (key, id) {
return { key: id.category, name: id.category };
});
});
addCategoryScreen.addToHeader(delimiterLine);
addCategoryScreen.addToFooter(delimiterLine);
addCategoryScreen.addToFooter(new ILib.ButtonLine("Add all")
.onUpdate(function () {
let isItemsToAdd = addCategoryScreen.data_items &&
addCategoryScreen.data_items.length > 0;
return {
unselectable: !isItemsToAdd
};
})
.onSelected(function () {
const items = addCategoryScreen.data_items;
return addAllItemsAndReturnToList.call(this, items, "Category")
})
);
}
}
// Order updating //
this.$updateInterfaceOrder = function $updateInterfaceOrder(newReorderings) {
const originalSetInterface = this.$originalSetInterface;
for (let stationId = 0; stationId < this.$stationIdToInterfacesMap.length; stationId++) {
let station = this.$idToStationMap[stationId];
if (!station || !station.isValid) {
this.$idToStationMap[stationId] = null;
this.$stationIdToInterfacesMap[stationId] = null;
continue;
}
let interfaces = this.$stationIdToInterfacesMap[stationId];
for (let key in interfaces) {
originalSetInterface.call(station, key, null); // hide all
}
}
this.$interfacePrefixes = this.$makeInterfacePrefixes(newReorderings);
this.$interfaceOrder = newReorderings.slice(); // NOT SPLICE
this.$unsavedInterfaceOrder.length = 0;
Array.prototype.push.apply(this.$unsavedInterfaceOrder, this.$interfaceOrder);
log("InterfaceReordering_Message", "New prefixes set: " + JSON.stringify(this.$interfacePrefixes));
for (let stationId = 0; stationId < this.$stationIdToInterfacesMap.length; stationId++) {
let station = this.$idToStationMap[stationId];
if (!station)
continue;
let interfaces = this.$stationIdToInterfacesMap[stationId];
for (let key in interfaces) {
let interfaceDefinition = interfaces[key];
station.setInterface(key, interfaceDefinition); // show with new rules
}
}
}
this.$makeInterfacePrefixes = function $makeInterfacePrefixes(interfaceOrder) {
return this.$makeInterfacePrefixes_V1(interfaceOrder);
}
/// Use zero width and hair spaces, on some computers they even don't display as "?"
this.$makeInterfacePrefixes_V0 = function $makeInterfacePrefixes_V0(interfaceOrder) {
// 0x200A - hair space character
// 0x200B - zero width character
const markerBeforeOthers = String.fromCharCode(0x200A) + String.fromCharCode(0x200B) + String.fromCharCode(0x200B);
const markerOthers = String.fromCharCode(0x200B) + String.fromCharCode(0x200A) + String.fromCharCode(0x200B);
const markerAfterOthers = String.fromCharCode(0x200B) + String.fromCharCode(0x200B) + String.fromCharCode(0x200A);
const markerDepth = String.fromCharCode(0x200B);
const interfacePrefixes = {/* key: prefix */ };
let beforeOthers = true;
let depthLevel = 1;
for (let i = 0; i < interfaceOrder.length; i++) {
let orderItem = interfaceOrder[i];
if (orderItem.type === "") {
beforeOthers = false;
depthLevel = 1;
}
else if (beforeOthers) {
interfacePrefixes[orderItem.key] = markerBeforeOthers + new Array(depthLevel).join(markerDepth);
}
else {
interfacePrefixes[orderItem.key] = markerAfterOthers + new Array(depthLevel).join(markerDepth);
}
depthLevel += 1;
}
interfacePrefixes["InterfaceReordering_OtherPrefix"] = markerOthers;
return interfacePrefixes;
}
this.$makeInterfacePrefixes_V1 = function $makeInterfacePrefixes_V1(interfaceOrder) {
const markerBeforeOthers = String.fromCharCode(0x1F, 0x1F);
const markerOthers = String.fromCharCode(0x1F, 0x200A);
const markerAfterOthers = String.fromCharCode(0x200A, 0x200A);
const markerDepth = String.fromCharCode(0x200A);
const interfacePrefixes = {/* key: prefix */ };
let beforeOthers = true;
let depthLevel = 1;
for (let i = 0; i < interfaceOrder.length; i++) {
let orderItem = interfaceOrder[i];
if (orderItem.type === "") {
beforeOthers = false;
depthLevel = 1;
}
else if (beforeOthers) {
interfacePrefixes[orderItem.key] = markerBeforeOthers + new Array(depthLevel).join(markerDepth);
}
else {
interfacePrefixes[orderItem.key] = markerAfterOthers + new Array(depthLevel).join(markerDepth);
}
depthLevel += 1;
}
interfacePrefixes["InterfaceReordering_OtherPrefix"] = markerOthers;
return interfacePrefixes;
}
|