Scripts/bb_system.js |
"use strict";
this.name = "BulletinBoardSystem";
this.author = "phkb";
this.copyright = "2017 phkb";
this.description = "Interface screen for localised or event-driven mission opportunities.";
this.license = "CC BY-NC-SA 4.0";
// CHECK: add a unaccepted mission for another system (ie not the current) - will it show up on the BB?
this._bbOpen = false; // flag to indicate the bulletin board has been opened
this._bbExiting = 0; // int to track what sort of exit from the board is taking place (1 = direct function key, 2 = from close menu)
this._maxpage = 0; // total number of pages of inbox to display
this._curpage = 0; // the current page of the inbox being displayed
this._msRows = 21; // rows to display on the mission screen
this._msCols = 32; // columns to display on the mission screen
this._displayType = 0; // controls the view.
this._displayPage = 0; // which page of the mission item are we showing
this._itemList = [];
this._routeMode = ""; // current route mode for long range chart (if ANA is installed)
this._itemColor = "yellowColor";
this._menuColor = "orangeColor";
this._exitColor = "yellowColor";
this._disabledColor = "darkGrayColor";
this._unavailableColor = "grayColor";
this._warningColor = "redColor";
this._onPathColor = "greenColor"; // colour for available items that are directly on your current course
this._nearPathColor = "0 0.6 0 1"; // colour for available items that are near to your current course
this._shuffleTries = 1; // how many times to shuffle the BB to make it as unsorted as possible.
this._updateRequired = false; // flag set after a new mission is added to the bb to indicate the interface entry needs to be updated
this._stationKeys = []; // array of worldscripts/stationkeys for the currently docked station
this._markers = ["NONE", "MARKER_X", "MARKER_PLUS", "MARKER_SQUARE", "MARKER_DIAMOND"];
this._completionTypes = ["AT_SOURCE", "AT_STATIONKEY", "ANYWHERE", "IMMEDIATE", "WHEN_DOCKED_SOURCE", "WHEN_DOCKED_STATIONKEY", "WHEN_DOCKED_ANYWHERE"];
this._lastChoice = ["", "", "", ""]; // stores the last choice on each of the mission screens
this._notCompleteText = ""; // text returned from the confirmCompleteCallback function
this._bbAdminName = {}; // names to attach to confirmation emails (when Email System is installed)
this._suspendedDestination = -1;
this._tempMarkers = -1;
this._nextID = 100;
this._eventRegister = {};
this._overlayDefault = {
name: "bb-overlay.png",
height: 546
};
this._overlay = this._overlayDefault;
this._backgroundDefault = "";
this._background = this._backgroundDefault;
this._holdItem = {};
this._mainMenuItems = []; // array of menu items to appear of the first page of the BB
this._alwaysShowBB = false;
this._showID = false; // flag which determines whether the ID number is shown on mission details page
this._nextContractOnMap = false;
this._nearPathRange = 7; // range (in LY) to consider a system to be "near"
this._useMarkers = 0; // how to use near system markers on BB list:
// 0 = no markers, just colours, 1 = markers only, no colours, 2 = markers and colours, 4 = turn off feature
this._oldVersion = 1.4;
this._storeHUD = "";
this._zoomDist = [{
dist: 45,
zoom: 3.5
},
{
dist: 40,
zoom: 3.2
},
{
dist: 35,
zoom: 2.9
},
{
dist: 30,
zoom: 2.5
},
{
dist: 25,
zoom: 2.2
},
{
dist: 20,
zoom: 1.7
},
{
dist: 15,
zoom: 1.4
},
{
dist: 7,
zoom: 1.0
},
];
// configuration settings for use in Lib_Config
this._bbConfig = {
Name: this.name,
Alias: "Bulletin Board System",
Display: "Display Options",
Alive: "_bbConfig",
Bool: {
B0: {
Name: "_showID",
Def: false,
Desc: "Show mission ID"
},
B1: {
Name: "_nextContractOnMap",
Def: false,
Desc: "'Next Contract' on map"
},
Info: "0 - Displays the internal mission ID on details page.\n1 - Shows the 'Next contract' option on all map screens."
},
SInt: {
S0: {
Name: "_nearPathRange",
Def: 7,
Min: 1,
Max: 15,
Desc: "Range of near path"
},
S1: {
Name: "_useMarkers",
Def: 0,
Min: 0,
Max: 3,
Desc: "Near system markers"
},
Info: "0 - Sets the range (in LY) defining a system as being near current path.\n1 - 0=No markers, 1=Markers Only, 2=Markers & Colors, 3=Turn off feature"
}
};
this._trueValues = ["yes", "1", 1, "true", true];
/* array Specifications
text Text to display on the menu
color Color of the item. Will default to this._menuColor (orangeColor)
unselectable Flag to control whether the item should be unselectable.
If true, color will be set to this._disabledColor (darkGreyColor)
autoRemove Flag to indicate the item should be removed from the menu when selected by the player.
worldScript WorldScript name for the callback function.
menuCallback Function to call when the user selects the item.
*/
this._data = []; // array of available and accepted missions
/* Array Specifications
data array
ID Numerical id of the mission
stationKey text key used for limiting new mission access to particular stations
will default to blank (all stations) if not provided. can include multiple items,
comma-separated (eg "galcop,chaotic")
description one line description of the mission (used on the main BB list)
source system where mission is available
sourceName name of the source system (auto-generated from the source value)
sourceGalaxy galaxy number where source system resides (will default to current galaxy)
destination system where mission must be completed: 0-255 for planets, -1 for interstellar,
256 for no destination
destinationName name of the destination system (auto-generated from the destination value)
destinationGalaxy galaxy number where the source system resides (will default to the current galaxy)
galaxy galaxy number where mission was created
details expanded description of the mission
manifestText text to display on the manifest screen
statusText text to include on the mission briefing screen when the mission is active.
Will default to manifestText when not supplied
expiry the time the mission must be completed by. -1 means unlimited time.
accepted boolean flag indicating the mission has been accepted by the player. default false.
percentComplete how much of the mission has been completed by the player
payment how much the player will be paid on completion of the mission
penalty how much the player will be penalised for not completing the mission
deposit (optional) how much the player needs to pay as a deposit for taking the mission.
This amount will be refunded if the mission is completed successfully.
Amount will be adjusted based on the percentage completed of the mission.
allowPartialComplete boolean flag that indicates whether the player can complete the mission with less than
the full percentage.
Payment will be scaled by the percentage completed. Penalties will also apply, again scaled
by the percentage completed.
For example, if the payment is 100 cr and the penalty 10 cr, and the player completes
70% of the mission, if they hand it in they would receive 70 cr (70% of 100), and the
penalty would be 3 cr (30% of 10), meaning their total payment would be 67 cr.
Default is false.
model role of a ship to use as the background on the mission details screen.
modelPersonality the entityPersonality assigned to the ship model.
spinModel True/false value indicating whether the ship model will rotate or not. The default to true.
background guiTextureSpecifier (name of a picture used as background)
overlay guiTextureSpecifier (name of a picture used as an overlay). Will default to the bulletin board
graphic when not set.
mapOverlay guiTextureSpecifier for map screen (name of a picture used as background).
Will default to the overlay setting (if provided) when not set, otherwise the bulletin board
graphic.
forceLongRangeChart boolean flag indicating whether the map screen for this mission will be forced to use
the long range chart. Default false, meaning the map will calculate the best zoom level
required based on the source and destination systems.
markerShape the shape of the destination system marker to use on the galactic chart (default "MARKER_PLUS").
Use "NONE" to leave off marking the chart.
markerColor the color of the marker on the galactic chart (default "redColor")
markerScale the scale of the marker on the galactic chart (default 1.0)
additionalMarkers array of dictionary items, defining extra markers that will be placed on the system map
system system ID where marker will be places
markerShape shape of the system marker (default "MARKER_PLUS")
markerColor color of the marker (default "redColor")
markerScale scale of the marker (default 1.0);
allowTerminate boolean flag to indicate whether the "Terminate mission" option will be available after
accepting the mission. (default true)
completionType what happens when mission is completed: "AT_SOURCE", "AT_STATIONKEY", "ANYWHERE",
"IMMEDIATE", "WHEN_DOCKED_SOURCE", "WHEN_DOCKED_STATIONKEY", "WHEN_DOCKED_ANYWHERE"
AT_SOURCE: player must return to the source system, dock at any station with the same
stationKey, open the mission and select "Complete mission"
AT_STATIONKEY: player can return to any system, dock at any station with the same
stationKey, open the mission and select "Complete mission"
ANYWHERE: player can return to any system, dock at any station, open the mission and
select "Complete mission"
IMMEDIATE: player is rewarded immediately when the mission is flagged as 100% complete -
player won't need to dock anywhere
WHEN_DOCKED_SOURCE: player is automatically rewarded as soon as they next dock at the source
station
WHEN_DOCKED_STATIONKEY: player is automatically rewarded as soon as they next dock at any
station with the same station key
WHEN_DOCKED_ANYWHERE: player is automatically rewarded as soon as they next dock at any station
(default "AT_SOURCE")
stopTimeAtComplete boolean flag to indicate that the clock will stop when the mission is flagged 100% complete.
Default false. This means that, for a completionType of "AT_SOURCE" the player has to return to
the original station within the allowed time in order to complete the mission. If this flag is
set to true, once the player completes the mission at the destination, they are free to take as
much time as they like to return to the original station and hand in their mission.
disablePercentDisplay boolean flag that allows the "Percent complete" item to be hidden on the mission details page.
Default false.
noEmails boolean flag that stops the transmission of confirmation emails
(if the Email System is installed)
statusValue value to display instead of the percentComplete value.
arrivalReportText text to display on the arrival report after the player completes mission and completionType
set to "WHEN_DOCKED_*"
customDisplayItems array of dictionary objects containing header/value key pairs to be displayed on the mission
screen as separate items.
customMenuItems array of dictionary objects containing additional items to be shown in the menu
text text to display on the menu
worldScript worldscript of the callback
callback function name of the callback object
condition function name of a callback object that will return either a blank,
meaning menu item is available, or some text, giving the reason why the item
is unavailable
activeOnly boolean indicating whether the menu item will only be visible when the
mission is active. default true.
autoRemove boolean indicating whether the menu item will be removed when selected
remoteDepositProcess boolean flag to indicate whether deduction of any deposit amount should be processed by the
BB or remotely.
Default is false, meaning the deposit will be deducted by the BB.
initiateCallback function name to callback when contract is accepted
confirmCompleteCallback function name to callback to check if a contract can be completed.
completedCallback function name to callback when the player flags the mission as completed
terminateCallback function name to callback when the player gives up on the mission
failedCallback function name to callback when the player fails a mission (called when the player docks)
manifestCallback function name to callback when the text on the manifest screen needs updating
availableCallback function name to callback when checking if this contract is available to the player
function should return either a blank string to indicate contract is available,
or a string with the reason why the contract is unavailable
if no callback is set, it is assumed contract is always available
worldScript name of worldscript containing the callback functions
postStatusMessages array of dictionary objects used to display text to the user after initiated, completed, or
terminated.
Will only be shown for completionTypes AT_SOURCE, AT_STATIONKEY, and ANYWHERE.
For any other completionType it is assumed the originating script will display additional info
the player, or the "arrivalReportText" will be used.
status Can be either initiated, completed, or terminated
return What to display after player pressed enter.
"item" to display the mission details,
"list" to display the mission list
"exit" to exit the BB completely
text Text to be displayed
background Background image to be used on the display
model Model to be shown on the display
overlay Overlay to be shown on the display
data object containing reference data for calling WS.
*/
//-------------------------------------------------------------------------------------------------------------
this.startUp = function () {
// load up player data
if (missionVariables.BBData) {
this._data = JSON.parse(missionVariables.BBData);
delete missionVariables.BBData;
this.$updateData();
}
}
//-------------------------------------------------------------------------------------------------------------
this.startUpComplete = function () {
// register our settings, if Lib_Config is present
if (worldScripts.Lib_Config) worldScripts.Lib_Config._registerSet(this._bbConfig);
this.$addAcceptedDate();
if (missionVariables.BBNextID) this._nextID = missionVariables.BBNextID;
// add a mission screen exception to Xenon UI
if (worldScripts.XenonUI) {
var wx = worldScripts.XenonUI;
wx.$addMissionScreenException("oolite-bbsystem-shortrangechart-map");
wx.$addMissionScreenException("oolite-bbsystem-longrangechart-map");
}
// add a mission screen exception to Xenon Redux UI
if (worldScripts.XenonReduxUI) {
var wxr = worldScripts.XenonReduxUI;
wxr.$addMissionScreenException("oolite-bbsystem-shortrangechart-map");
wxr.$addMissionScreenException("oolite-bbsystem-longrangechart-map");
}
if (worldScripts.DisplayCurrentCourse) {
var dcc = worldScripts.DisplayCurrentCourse;
dcc._screenIDList.push("oolite-bbsystem-shortrangechart-map");
dcc._screenIDList.push("oolite-bbsystem-longrangechart-map");
}
this._suspendedDestination = -1;
this._tempMarkers = -1;
this._hudHidden = false;
if (missionVariables.BBUseMarkers) this._useMarkers = parseInt(missionVariables.BBUseMarkers);
if (missionVariables.BBNearPathRange) this._nearPathRange = parseInt(missionVariables.BBNearPathRange);
if (missionVariables.BBOldVersion) this._oldVersion = parseFloat(missionVariables.BBOldVersion);
if (missionVariables.BBNextContractOnMap) this._nextContractOnMap = this._trueValues.indexOf(missionVariables.BBNextContractOnMap) >= 0 ? true : false;
if (player.ship.docked) this.$initInterface(player.ship.dockedStation);
// dud data cleanup
//for (var i = 1; i < 10; i++) {
// for (var j = 0; j <= 255; j++) {
// mission.unmarkSystem({system:j, name:"GalCopBB_Missions" + "_" + i});
// }
// }
this.$refreshManifest();
this.$dataCleanup();
}
//-------------------------------------------------------------------------------------------------------------
this.shipWillDockWithStation = function (station) {
this.$triggerBBEvent("shipWillDockWithStation_start", station);
this.$checkForCompleteOnDock(station);
this.$initInterface(station);
this.$triggerBBEvent("shipWillDockWithStation_end", station);
}
//-------------------------------------------------------------------------------------------------------------
this.playerWillSaveGame = function () {
missionVariables.BBOldVersion = this._oldVersion;
missionVariables.BBNextID = this._nextID;
if (this._data.length > 0) {
missionVariables.BBData = JSON.stringify(this._data);
} else {
delete missionVariables.BBData;
}
missionVariables.BBNearPathRange = this._nearPathRange;
missionVariables.BBUseMarkers = this._useMarkers;
missionVariables.BBNextContractOnMap = this._nextContractOnMap;
}
//-------------------------------------------------------------------------------------------------------------
this.guiScreenWillChange = function (to, from) {
// force the manifest entries to update
// this keeps the "number of hours remaining" value up to date.
if (to === "GUI_SCREEN_MANIFEST") {
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].accepted === true) {
if (this._data[i].manifestCallback != "") {
if (worldScripts[this._data[i].worldScript] && worldScripts[this._data[i].worldScript][this._data[i].manifestCallback]) {
worldScripts[this._data[i].worldScript][this._data[i].manifestCallback](this._data[i].ID);
}
}
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
this.guiScreenChanged = function (to, from) {
var p = player.ship;
if (from === "GUI_SCREEN_MISSION" && this._bbOpen) {
this._bbOpen = false;
//if (this._hudHidden === false && p.hudHidden === true) p.hudHidden = this._hudHidden;
if (this._suspendedDestination >= 0) p.targetSystem = this._suspendedDestination;
this._suspendedDestination = -1;
if (this._tempMarkers >= 0) this.$removeChartMarker(this._tempMarkers);
this._tempMarkers = -1;
}
if (guiScreen === "GUI_SCREEN_INTERFACES" || this._updateRequired === true) {
// update the interfaces screen
this._updateRequired = false;
if (p.dockedStation != null) {
if (p.docked) this.$initInterface(p.dockedStation);
}
}
}
//-------------------------------------------------------------------------------------------------------------
this.missionScreenOpportunity = function () {
if (this._bbExiting === 1) {
this._bbExiting = 0;
this.$triggerBBEvent("exit");
}
this._bbExiting = 0;
}
//-------------------------------------------------------------------------------------------------------------
this.shipLaunchedFromStation = function (station) {
if (this._bbExiting === 1) {
this._bbExiting = 0;
this.$triggerBBEvent("launchExit", station);
}
this._bbExiting = 0;
}
//-------------------------------------------------------------------------------------------------------------
this.shipWillEnterWitchspace = function (cause, destination) {
// clear out any unaccepted missions whenever we do a witchspace jump
for (var i = this._data.length - 1; i >= 0; i--) {
if (this._data[i].source === system.ID && this._data[i].accepted === false &&
(!this._data[i].hasOwnProperty("keepAvailable") || this._data[i].keepAvailable == false))
this.$removeBBMission(this._data[i].ID);
}
// reset the BB admin name dictionary so new names will be generated for this system
this._bbAdminName = {};
// reset the station keys
this._stationKeys = [];
}
//-------------------------------------------------------------------------------------------------------------
// adds a mission to the BB.
this.$addBBMission = function $addBBMission(bbObj) {
var truetypes = ["yes", "1", "true", true, 1, -1];
var falsetypes = ["no", "0", "false", false, 0];
var src = system.ID;
if (bbObj.hasOwnProperty("source")) {
if (parseInt(bbObj.source) > 255 || parseInt(bbObj.source) < 0) {
throw "Invalid BB mission settings: 'source' system ID (" + bbObj.source + ") must be between 0 and 255.";
}
src = bbObj.source;
}
if (bbObj.hasOwnProperty("destination") === false || parseInt(bbObj.destination) > 256 || parseInt(bbObj.destination) < -1) {
throw "Invalid BB mission settings: 'destination' system ID (" + bbObj.destination + ") must be supplied and between -1 and 256.";
}
if (bbObj.hasOwnProperty("description") === false || bbObj.description === "") {
throw "Invalid BB mission settings: 'description' must be supplied.";
}
if (bbObj.hasOwnProperty("details") === false || bbObj.details === "") {
throw "Invalid BB mission settings: 'details' must be supplied.";
}
if (bbObj.hasOwnProperty("payment") === true && bbObj.payment < 0) {
throw "Invalid BB mission settings: 'payment' must be greater than or equal to 0.";
}
if (bbObj.hasOwnProperty("expiry") === false || bbObj.expiry === 0) {
throw "Invalid BB mission settings: 'expiry' must be supplied.";
}
if (bbObj.expiry > 0 && bbObj.expiry < clock.adjustedSeconds) {
throw "Invalid BB mission settings: 'expiry' must be in the future.";
}
if (bbObj.hasOwnProperty("worldScript") === false || bbObj.worldScript === "") {
throw "Invalid BB mission settings: 'worldScript' must be supplied.";
}
// work out some defaults, and if they've been overridden
// completionType
var completeType = "AT_SOURCE";
if (bbObj.hasOwnProperty("completionType") && bbObj.completionType != "") {
if (this._completionTypes.indexOf(bbObj.completionType) >= 0) {
completeType = bbObj.completionType;
} else {
throw "Invalid BB mission settings: unrecognised 'completionType' setting (" + bbObj.completionType + "). Must be one of " + this._completionTypes;
}
}
var stopTime = false;
if (bbObj.hasOwnProperty("stopTimeAtComplete") && truetypes.indexOf(bbObj.stopTimeAtComplete) >= 0) {
stopTime = true;
}
// markerShape
var markShape = "MARKER_PLUS";
if (bbObj.hasOwnProperty("markerShape") && bbObj.markerShape != "") {
if (this._markers.indexOf(bbObj.markerShape) >= 0) {
markShape = bbObj.markerShape;
} else {
throw "Invalid BB mission settings: unrecognised 'markerShape' setting (" + bbObj.markerShape + "). Must be one of " + this._markers;
}
}
// validate any postStatusMessages
if (bbObj.hasOwnProperty("postStatusMessages")) {
var statusTypes = ["initiated", "completed", "terminated"];
var list = bbObj.postStatusMessages;
if (list && list.length > 0) {
for (var i = 0; i < list.length; i++) {
if (list[i].hasOwnProperty("status") === false) {
throw "Invalid BB mission settings: postStatusMessages does not include 'status' property.";
}
if (statusTypes.indexOf(list[i].status) === -1) {
throw "Invalid BB mission settings: postStatusMessages 'status' value (" + list[i].status + ") not recognised. Must be one of " + statusTypes;
}
if (list[i].hasOwnProperty("text") === false) {
throw "Invalid BB mission settings: postStatusMessages does not include 'text' property.";
}
if (list[i].text === "") {
throw "Invalid BB mission settings: postStatusMessages 'text' value is blank";
}
}
}
}
var addMarkers = [];
if (bbObj.hasOwnProperty("additionalMarkers") === true) {
// make sure each additional marker is valid
if (Array.isArray(bbObj.additionalMarkers) === true) {
for (var i = 0; i < bbObj.additionalMarkers.length; i++) {
var item = bbObj.additionalMarkers[i];
if (item.hasOwnProperty("system") === true) {
var def = {};
def["system"] = item.system;
if (item.hasOwnProperty("markerShape") === true) {
if (this._markers.indexOf(item.markerShape) >= 0) {
def["markerShape"] = item.markerShape;
} else {
throw "Invalid BB mission settings: unrecognised 'markerShape' setting (" + item.markerShape + "). Must be one of " + this._markers;
}
} else {
def["markerShape"] = "MARKER_PLUS";
}
if (item.hasOwnProperty("markerColor") === true) {
def["markerColor"] = item.markerColor;
} else {
def["markerColor"] = "redColor";
}
if (item.hasOwnProperty("MARKER_SCALE") === true) {
def["markerScale"] = item.markerScale;
} else {
def["markerScale"] = 1.0;
}
addMarkers.push(def);
}
}
}
}
var id = 0;
if (bbObj.hasOwnProperty("ID") && isNaN(bbObj.ID) === false && parseInt(bbObj.ID) > 0) {
id = parseInt(bbObj.ID);
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].ID === id) {
throw "Invalid BB mission settings: ID " + id + " is already in use!";
}
}
} else {
id = this.$nextID();
}
this._data.push({
ID: id,
stationKey: (bbObj.stationKey && bbObj.stationKey != "" ? bbObj.stationKey : ""),
source: src,
sourceName: System.systemNameForID(src),
sourceGalaxy: galaxyNumber,
destination: bbObj.destination,
destinationName: this.$systemNameForID(bbObj.destination),
destinationGalaxy: galaxyNumber,
description: bbObj.description,
details: bbObj.details,
manifestText: (bbObj.hasOwnProperty("manifestText") ? bbObj.manifestText : ""),
originalManifestText: (bbObj.hasOwnProperty("manifestText") ? bbObj.manifestText : ""),
statusText: (bbObj.hasOwnProperty("statusText") ? bbObj.statusText : ""),
payment: (bbObj.hasOwnProperty("payment") ? bbObj.payment : 0),
penalty: (bbObj.hasOwnProperty("penalty") && bbObj.penalty > 0 ? bbObj.penalty : 0),
deposit: (bbObj.hasOwnProperty("deposit") && parseInt(bbObj.deposit) > 0 ? parseInt(bbObj.deposit) : 0),
allowPartialComplete: (bbObj.hasOwnProperty("allowPartialComplete") && truetypes.indexOf(bbObj.allowPartialComplete) >= 0 ? true : false),
expiry: bbObj.expiry,
playAcceptedSound: (!bbObj.hasOwnProperty("playAcceptedSound") || truetypes.indexOf(bbObj.playAcceptedSound) >= 0 ? true : false),
accepted: (bbObj.hasOwnProperty("accepted") && truetypes.indexOf(bbObj.accepted) >= 0 ? true : false),
allowTerminate: (bbObj.hasOwnProperty("allowTerminate") && falsetypes.indexOf(bbObj.allowTerminate) >= 0 ? false : true),
percentComplete: (bbObj.hasOwnProperty("percentComplete") && bbObj.percentComplete > 0 ? bbObj.percentComplete : 0.0),
completionType: completeType,
stopTimeAtComplete: stopTime,
completionTime: (bbObj.hasOwnProperty("completionTime") && bbObj.completionTime > 0 ? bbObj.completionTime : 0),
arrivalReportText: (bbObj.hasOwnProperty("arrivalReportText") ? bbObj.arrivalReportText : ""),
model: (bbObj.hasOwnProperty("model") ? bbObj.model : ""),
modelPersonality: (bbObj.hasOwnProperty("modelPersonality") && parseInt(bbObj.modelPersonality) > 0 ? bbObj.modelPersonality : 0),
spinModel: (bbObj.hasOwnProperty("spinModel") && falsetypes(bbObj.spinModel) ? false : true),
background: (bbObj.hasOwnProperty("background") ? bbObj.background : ""),
overlay: (bbObj.hasOwnProperty("overlay") ? bbObj.overlay : ""),
mapOverlay: (bbObj.hasOwnProperty("mapOverlay") ? bbObj.mapOverlay : (bbObj.hasOwnProperty("overlay") ? bbObj.overlay : "")),
forceLongRangeChart: (bbObj.hasOwnProperty("forceLongRangeChart") && truetypes.indexOf(bbObj.forceLongRangeChart) >= 0 ? true : false),
markerShape: markShape,
markerColor: (bbObj.hasOwnProperty("markerColor") ? bbObj.markerColor : "redColor"),
markerScale: (bbObj.hasOwnProperty("markerScale") && bbObj.markerScale >= 0.5 && bbObj.markerScale <= 2.0 ? bbObj.markerScale : 1.0),
additionalMarkers: addMarkers,
disablePercentDisplay: (bbObj.hasOwnProperty("disablePercentDisplay") && truetypes.indexOf(bbObj.disablePercentDisplay) >= 0 ? true : false),
noEmails: (bbObj.hasOwnProperty("noEmails") && bbObj.noEmails == true ? true : false),
statusValue: (bbObj.hasOwnProperty("statusValue") && bbObj.statusValue != "" ? bbObj.statusValue : ""),
customDisplayItems: (bbObj.hasOwnProperty("customDisplayItems") ? bbObj.customDisplayItems : ""),
customMenuItems: (bbObj.hasOwnProperty("customMenuItems") ? bbObj.customMenuItems : ""),
remoteDepositProcess: (bbObj.hasOwnProperty("remoteDepositProcess") && truetypes.indexOf(bbObj.remoteDepositProcess) >= 0 ? true : false),
initiateCallback: (bbObj.hasOwnProperty("initiateCallback") ? bbObj.initiateCallback : ""),
confirmCompleteCallback: (bbObj.hasOwnProperty("confirmCompleteCallback") ? bbObj.confirmCompleteCallback : ""),
completedCallback: (bbObj.hasOwnProperty("completedCallback") ? bbObj.completedCallback : ""),
terminateCallback: (bbObj.hasOwnProperty("terminateCallback") ? bbObj.terminateCallback : ""),
failedCallback: (bbObj.hasOwnProperty("failedCallback") ? bbObj.failedCallback : ""),
manifestCallback: (bbObj.hasOwnProperty("manifestCallback") ? bbObj.manifestCallback : ""),
availableCallback: (bbObj.hasOwnProperty("availableCallback") ? bbObj.availableCallback : ""),
bonusCalculationCallback: (bbObj.hasOwnProperty("bonusCalculationCallback") ? bbObj.bonusCalculationCallback : ""),
worldScript: bbObj.worldScript,
postStatusMessages: (bbObj.hasOwnProperty("postStatusMessages") ? bbObj.postStatusMessages : []),
data: (bbObj.hasOwnProperty("data") ? bbObj.data : null),
acceptedDate: (bbObj.hasOwnProperty("accepted") && truetypes.indexOf(bbObj.accepted) >= 0 ? clock.adjustedSeconds : 0),
keepAvailable: (bbObj.hasOwnProperty("keepAvailable") ? bbObj.keep : false)
});
this._updateRequired = true;
// auto-accepted items should get their manifest entry updated immediately
if (bbObj.hasOwnProperty("accepted") && bbObj.accepted === true) {
this.$addManifestEntry(id);
// send an email (if installed)
this.$sendEmail(player.ship.dockedStation, "accepted", id);
// update f4 interface entry, if docked
if (player.ship.docked) this.$initInterface(player.ship.dockedStation);
}
this.$triggerBBEvent("missionAdded");
return id;
}
//-------------------------------------------------------------------------------------------------------------
// adds an item to the BB main menu
this.$addMainMenuItem = function $addMainMenuItem(mnu) {
if (mnu.hasOwnProperty("text") === false || mnu.text === "") {
throw "Invalid BB menu setting: 'text' property must be supplied and not blank.";
}
if (mnu.hasOwnProperty("worldScript") === false || mnu.worldScript === "") {
throw "Invalid BB menu setting: 'worldScript' property must be supplied and not blank.";
}
if (mnu.hasOwnProperty("menuCallback") === false || mnu.menuCallback === "") {
throw "Invalid BB menu setting: 'menuCallback' property must be supplied and not blank.";
}
this._mainMenuItems.push({
text: mnu.text,
color: (mnu.hasOwnProperty("color") ? mnu.color : this._menuColor),
unselectable: (mnu.hasOwnProperty("unselectable") ? mnu.unselectable : false),
autoRemove: (mnu.hasOwnProperty("autoRemove") ? mnu.autoRemove : false),
worldScript: mnu.worldScript,
menuCallback: mnu.menuCallback
})
}
//-------------------------------------------------------------------------------------------------------------
// removes an item from the main menu based on worldScript name and function callback name
this.$removeMainMenuItem = function $removeMainMenuItem(wsName, fnName) {
for (var i = this._mainMenuItems.length - 1; i >= 0; i--) {
if (this._mainMenuItems[i].worldScript === wsName && this._mainMenuItems[i].menuCallback === fnName) {
this._mainMenuItems.splice(i, 1);
}
}
}
//-------------------------------------------------------------------------------------------------------------
// sets the default BB background to a new guiTextureSpecifier
this.$setBackgroundDefault = function $setBackgroundDefault(gui) {
this._background = gui;
}
//-------------------------------------------------------------------------------------------------------------
// resets the default BB background back to the default
this.$resetBackgroundDefault = function $resetBackgroundDefault() {
this._background = this._backgroundDefault;
}
//-------------------------------------------------------------------------------------------------------------
// sets the default BB overlay to a new guiTextureSpecifier
this.$setOverlayDefault = function $setOverlayDefault(gui) {
this._overlay = gui;
}
//-------------------------------------------------------------------------------------------------------------
// resets the default BB overlay back to the default
this.$resetOverlayDefault = function $resetOverlayDefault() {
this._overlay = this._overlayDefault;
}
//-------------------------------------------------------------------------------------------------------------
// registers a worldscript function to be called whenever a particular event occurs
this.$registerBBEvent = function $registerBBEvent(wsName, fnName, eventName) {
var list = this._eventRegister[eventName];
if (!list) list = [];
var found = false;
for (var i = 0; i < list.length; i++) {
if (list[i].worldScript === wsName && list[i].functionName === fnName) {
found = true;
break;
}
}
if (found === false) {
list.push({
worldScript: wsName,
functionName: fnName
});
this._eventRegister[eventName] = list;
}
}
//-------------------------------------------------------------------------------------------------------------
this.$unregisterBBEvent = function $unregisterBBEvent(wsName, fnName, eventName) {
var list = this._eventRegister[eventName];
if (!list) return;
for (var i = 0; i < list.length; i++) {
if (list[i].worldScript === wsName && list[i].functionName === fnName) {
list.splice(i, 1);
break;
}
}
}
//-------------------------------------------------------------------------------------------------------------
// performs all callbacks for a given event
this.$triggerBBEvent = function $triggerBBEvent(eventName, param) {
if (!this._eventRegister) return;
var list = this._eventRegister[eventName];
if (!list) return;
for (var i = 0; i < list.length; i++) {
try {
if (param) {
if (worldScripts[list[i].worldScript] && worldScripts[list[i].worldScript][list[i].functionName]) {
worldScripts[list[i].worldScript][list[i].functionName](param);
}
} else {
if (worldScripts[list[i].worldScript] && worldScripts[list[i].worldScript][list[i].functionName]) {
worldScripts[list[i].worldScript][list[i].functionName]();
}
}
} catch (err) {
log(this.name, "!!ERROR: Unable to call event callback. Event:" + eventName + ", WS:" + list[i].worldScript + " FN:" + list[i].functionName + " Error:" + err);
}
}
}
//-------------------------------------------------------------------------------------------------------------
// external function call to reorder the list randomly
this.$shuffleBBList = function $shuffleBBList() {
for (var i = 0; i < this._shuffleTries; i++)
this._data.sort(function (a, b) {
return Math.random() - 0.5;
}); // shuffle order so it isn't always the same variant being checked first
}
//-------------------------------------------------------------------------------------------------------------
// external function call to remove a particular custom menu item from a BB entry
this.$removeCustomMenuItem = function $removeCustomMenuItem(bbID, index) {
var itm = this.$getItem(bbID);
if (itm.customMenuItems) {
var mnu = item.customMenuItems;
if (mnu != "" && mnu.length > index) {
mnu.splice(index, 1);
}
}
}
//-------------------------------------------------------------------------------------------------------------
// looks for any completed missions whose completion method is "WHEN_DOCKED_SOURCE", "WHEN_DOCKED_STATIONKEY" or "WHEN_DOCKED_ANYWHERE"
this.$checkForCompleteOnDock = function $checkForCompleteOnDock(station) {
this._stationKeyDefault = this.$getStationKeyDefault(station);
for (var i = this._data.length - 1; i >= 0; i--) {
var item = this._data[i];
// look for any active missions
if (item.accepted === true) {
// check if this mission is complete within the time required, and if the completion type is one of the "DOCKED" types.
// logic: if the mission is flagged as completed (percentComplete = 1), and we can only set percentComplete to 1 if it's still within the time allowed (see $updateBBMissionPercentage)
if (item.percentComplete === 1 && this.$isMissionExpired(item) === false) { // clock.adjustedSeconds < item.expiry
if (((item.completionType === "WHEN_DOCKED_SOURCE" && item.source === system.ID && (item.stationKey === "" || this.$checkMissionStationKey(item.worldScript, station, item.stationKey) === true)) ||
(item.completionType === "WHEN_DOCKED_STATIONKEY" && (item.stationKey === "" || this.$checkMissionStationKey(item.worldScript, station, item.stationKey) === true)) ||
item.completionType === "WHEN_DOCKED_ANYWHERE")) {
var result = "";
if (item.confirmCompleteCallback) {
if (worldScripts[item.worldScript] && worldScripts[item.worldScript][item.confirmCompleteCallback]) {
result = worldScripts[item.worldScript][item.confirmCompleteCallback](item.ID);
}
}
if (result === "") {
// complete the mission
this.$completeBBMission(item.ID);
} else {
// add the error details to the arrival report.
player.addMessageToArrivalReport(result);
// fail the mission
this.$failedBBMission(item.ID, false);
}
}
} else if (clock.adjustedSeconds >= item.expiry && item.expiry > 0) {
// too late, so fail the mission
this.$failedBBMission(item.ID, false);
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
// returns the current completed percentage for a mission
this.$percentCompleted = function $percentCompleted(bbID) {
var itm = this.$getItem(bbID);
if (itm) return itm.percentComplete;
return 0;
}
//-------------------------------------------------------------------------------------------------------------
// updates the completed percentage of a mission
this.$updateBBMissionPercentage = function $updateBBMissionPercentage(bbID, pct) {
var itm = this.$getItem(bbID);
// only update missions that are still active (if it's expired, no further updates should happen)
if (itm && (clock.adjustedSeconds < itm.expiry || itm.expiry === -1)) {
itm.percentComplete = pct;
// tell the originator to update their manifest text
if (itm.manifestCallback != "") {
if (worldScripts[itm.worldScript] && worldScripts[itm.worldScript][itm.manifestCallback]) {
worldScripts[itm.worldScript][itm.manifestCallback](itm.ID);
}
}
// check if we've completed the mission and the completion type is set to "IMMEDIATE"
if (pct === 1) {
itm.completionTime = clock.adjustedSeconds;
switch (itm.completionType) {
case "IMMEDIATE":
// theoretically there should be no need to call the confirmCompleteCallback here,
// as the calling worldScript has just flagged the mission complete.
this.$completeBBMission(bbID);
// if the bounty system is installed, rerun the process to take a snapshot of credits/score
if (worldScripts.BountySystem_Deferred) {
if (player.ship.isInSpace) worldScripts.BountySystem_Deferred.$setPlayerBaseline();
}
break;
case "AT_SOURCE":
case "WHEN_DOCKED_SOURCE":
this.$revertChartMarker(bbID);
break;
case "AT_STATIONKEY":
case "WHEN_DOCKED_STATIONKEY":
case "WHEN_DOCKED_ANYWHERE":
case "ANYWHERE":
this.$removeChartMarker(bbID);
break;
}
}
return;
}
}
//-------------------------------------------------------------------------------------------------------------
// updates the manifest screen text for a particular mission
// this should be called by the originating script when the manifestCallback routine is called
this.$updateBBManifestText = function $updateBBManifestText(bbID, newtext) {
var item = this.$getItem(bbID);
item.manifestText = newtext;
// grab a copy of the first version of the manifest so we can use it in emails.
if (item.originalManifestText === "") item.originalManifestText = newtext;
this.$refreshManifest();
}
//-------------------------------------------------------------------------------------------------------------
// updates the status text for a particular mission
this.$updateBBStatusText = function $updateBBStatusText(bbID, newtext) {
var item = this.$getItem(bbID);
item.statusText = newtext;
}
//-------------------------------------------------------------------------------------------------------------
// executes various functions when a mission is completed
this.$completeBBMission = function $completeBBMission(bbID) {
var item = this.$getItem(bbID);
// execute the callback
if (item.completedCallback != "") {
if (worldScripts[item.worldScript] && worldScripts[item.worldScript][item.completedCallback]) {
worldScripts[item.worldScript][item.completedCallback](item.ID);
}
}
// pay the player their payment
if (item.payment != 0 || item.bonusCalculationCallback != "") {
// calculate the payment amount. normally percentComplete will be 1.0, but the "allowPartialComplete" flag means we should always scale the figure
var total = item.payment * item.percentComplete;
// add the deposit, if present
//total += (item.deposit && item.deposit > 0 ? item.deposit * item.percentComplete : 0); -- deposit amount should be included in payment amount
// apply the factored penalty, if present. Completing 30% of a mission means 70% of the penalty will apply.
if (item.allowPartialComplete && item.penalty > 0) total -= item.penalty * (1 - item.percentComplete);
if (item.percentComplete === 1 && item.bonusCalculationCallback !== "") {
if (worldScripts[item.worldScript] && worldScripts[item.worldScript][item.bonusCalculationCallback]) {
var bonus = worldScripts[item.worldScript][item.bonusCalculationCallback](item.ID);
total += bonus;
}
}
player.credits += total;
if (total !== 0) {
if (player.ship.status === "STATUS_DOCKING") {
// work out what message to add to the arrival report
var msg = "";
if (item.arrivalReportText != "") {
msg = item.arrivalReportText;
} else {
msg = expandDescription("[bb-arrival-completed]", {
description: item.description,
payment: formatCredits(total, true, true)
});
}
player.addMessageToArrivalReport(msg);
} else {
player.consoleMessage(expandDescription("[bb-console-completed]", {
description: item.description,
payment: formatCredits(total, true, true)
}), 5);
}
}
}
// send an email (if installed)
this.$sendEmail(player.ship.dockedStation, "success", item.ID, total);
// remove item from manifest screen
this.$removeManifestEntry(item.ID);
// remove the mission from the list
this.$removeBBMission(item.ID);
// update the interface screen entry
if (player.ship.dockedStation) this.$initInterface(player.ship.dockedStation);
}
//-------------------------------------------------------------------------------------------------------------
// fails the mission (either through manual termination, or by docking after the time expires)
this.$failedBBMission = function $failedBBMission(bbID, manual) {
var item = this.$getItem(bbID);
var pen = 0;
// call the failed function, if supplied
if (manual === false && item.failedCallback !== "") {
if (worldScripts[item.worldScript] && worldScripts[item.worldScript][item.failedCallback]) {
worldScripts[item.worldScript][item.failedCallback](bbID);
}
}
// call the terminate function, if supplied
if (manual === true && item.terminateCallback !== "") {
if (worldScripts[item.worldScript] && worldScripts[item.worldScript][item.terminateCallback]) {
worldScripts[item.worldScript][item.terminateCallback](bbID);
}
}
// if there was a penalty for failing the mission, penalise the player now
if (item.penalty != 0) {
pen = Math.round(item.penalty * (1 - item.percentComplete));
// if they've partially completed the mission, and the specs allow for it, give the player the amount they've completed
if (item.allowPartialComplete && item.payment > 0 && item.percentComplete > 0) {
pen -= (item.payment & item.percentComplete);
}
player.credits -= pen;
}
if (player.ship.status === "STATUS_DOCKING") {
// work out what message to add to the arrival report
var msg = "";
if (pen === 0) {
msg = expandDescription("[bb-arrival-failed-nopenalty]", {
description: item.description
});
} else if (pen > 0) {
msg = expandDescription("[bb-arrival-failed-penalty]", {
description: item.description,
penalty: formatCredits(pen, true, true)
});
} else {
msg = expandDescription("[bb-arrival-failed-payment]", {
description: item.description,
payment: formatCredits(Math.abs(pen), true, true)
})
}
player.addMessageToArrivalReport(msg);
} else {
var type = "failed";
if (manual === true) type = "terminate";
if (pen === 0) {
player.consoleMessage(expandDescription("[bb-console-" + type + "-nopenalty]", {
description: item.description
}), 5);
} else if (pen > 0) {
player.consoleMessage(expandDescription("[bb-console-" + type + "-penalty]", {
description: item.description,
penalty: formatCredits(pen, true, true)
}), 5);
} else {
player.consoleMessage(expandDescription("[bb-console-" + type + "-payment]", {
description: item.description,
penalty: formatCredits(Math.abs(pen), true, true)
}), 5);
}
}
// send an email (if installed)
if (manual === false) {
// if the manual flag is false (ie not from the player manually terminating the mission)
this.$sendEmail(player.ship.dockedStation, "fail", item.ID, pen);
} else {
// send an email (if installed)
this.$sendEmail(player.ship.dockedStation, "terminated", item.ID, pen);
}
// remove item from manifest screen
this.$removeManifestEntry(item.ID);
this.$removeBBMission(item.ID);
this.$initInterface(player.ship.dockedStation);
}
//-------------------------------------------------------------------------------------------------------------
// removes a mission from the datalist
this.$removeBBMission = function $removeBBMission(bbID) {
for (var i = this._data.length - 1; i >= 0; i--) {
if (this._data[i].ID === bbID) {
this.$removeChartMarker(bbID);
this._data.splice(i, 1);
return;
}
}
}
//-------------------------------------------------------------------------------------------------------------
// returns the next ID number for new missions
this.$nextID = function $nextID() {
var ok = false;
do {
this._nextID += 1;
if (this._nextID > 30000) this._nextID = 1;
ok = true;
// is this id available?
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].ID === this._nextID) {
// dang it. lets do this loop again
ok = false;
break;
}
}
} while (ok === false);
var result = this._nextID;
return result;
}
//-------------------------------------------------------------------------------------------------------------
// counts the number of missions available at the current station
this.$countAvailable = function $countAvailable(station) {
var avail = 0;
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].source === system.ID && this._data[i].accepted === false && (this._data[i].expiry === -1 || this.$isMissionExpired(this._data[i]) === false) &&
(this._data[i].stationKey === "" || this.$checkMissionStationKey(this._data[i].worldScript, station, this._data[i].stationKey) === true))
avail += 1;
}
return avail;
}
//-------------------------------------------------------------------------------------------------------------
// counts the number of active missions (but not expired missions)
this.$countActive = function $countActive() {
var active = 0;
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].accepted === true) active += 1;
}
return active;
}
//-------------------------------------------------------------------------------------------------------------
// returns true if a mission has expired, otherwise false
this.$isMissionExpired = function $isMissionExpired(itm) {
if (itm.expiry === -1) return false;
var checkTime = 0;
if (itm.completionTime != 0 && itm.stopTimeAtComplete === true) {
checkTime = itm.completionTime;
} else {
checkTime = clock.adjustedSeconds + (itm.accepted === false ? this.$estimatedMissionTime(itm) : 0);
// if we're at the destination system, give a little bit of leeway
if (itm.destination === system.ID) checkTime -= 1800;
}
if (checkTime < itm.expiry && itm.expiry > 0) {
return false;
} else {
return true;
}
}
//-------------------------------------------------------------------------------------------------------------
// returns true if the mission is going to be hard to complete within the time frame, otherwise false
this.$isMissionCloseToExpiry = function $isMissionCloseToExpiry(itm) {
var result = false;
// assume anything in another galaxy is close to expiry
if (itm.destinationGalaxy !== galaxyNumber) return true;
var time = this.$estimatedMissionTime(itm);
if (time === -1) return result;
// check if the destination system is not the current system
if (itm.destination != system.ID && itm.destination <= 255 && itm.destination >= 0 && itm.percentComplete < 1) {
if (clock.adjustedSeconds + time > itm.expiry && itm.expiry > 0) result = true;
} else {
if (itm.expiry > 0 && itm.expiry - clock.adjustedSeconds < 1800 && itm.percentComplete < 1) result = true;
}
return result;
}
//-------------------------------------------------------------------------------------------------------------
// performs callback to determine if mission is actually available to the player
this.$isMissionAvailable = function $isMissionAvailable(itm) {
if (itm.accepted === true) return true;
if (itm.hasOwnProperty("availableCallback") && itm.availableCallback != "") {
if (!worldScripts[itm.worldScript] || !worldScripts[itm.worldScript][itm.availableCallback]) return false;
var test = worldScripts[itm.worldScript][itm.availableCallback](itm.ID);
if (test === "") {
return true;
} else {
return false;
}
} else {
return true;
}
}
//-------------------------------------------------------------------------------------------------------------
// performs the availableCallback and returns the unavailability reason, if any
this.$missionUnavailableReason = function $missionUnavailableReason(bbID) {
var itm = this.$getItem(bbID);
if (itm.accepted === true) return "";
if (itm.hasOwnProperty("availableCallback") === false || itm.availableCallback === "") return "";
if (!worldScripts[itm.worldScript] || !worldScripts[itm.worldScript][itm.availableCallback]) return "";
return worldScripts[itm.worldScript][itm.availableCallback](bbID);
}
//-------------------------------------------------------------------------------------------------------------
// estimated amount of time the mission is likely to take
this.$estimatedMissionTime = function $estimatedMissionTime(itm) {
if (itm.percentComplete === 1 && ((itm.stopTimeAtComplete === true && (itm.completionTime < itm.expiry || itm.expiry === -1)) ||
(itm.stopTimeAtComplete === false && (clock.adjustedSeconds < itm.expiry || itm.expiry === -1))))
return -1;
var time = 0;
// first, check if the destination system is not the current system
if (itm.destination != system.ID && itm.destination <= 255 && itm.destination >= 0 && itm.percentComplete < 1) {
// calculate time for a return journey
var info = null;
var route = null;
// outward journey
if (itm.destination != system.ID && itm.destination >= 0 && itm.destination <= 255) {
info = System.infoForSystem(galaxyNumber, itm.destination);
route = system.info.routeToSystem(info, "OPTIMIZED_BY_TIME");
if (route) {
time += route.time * 3600;
// plus 15 minutes transit time in each system
time += route.route.length * 900;
}
} else {
time += 24 * 3600;
}
// return journey (if stopTimeAtComplete is false)
if (itm.stopTimeAtComplete === false && route != null) {
switch (itm.completeType) {
case "AT_SOURCE":
case "WHEN_DOCKED_SOURCE":
if (itm.source === system.ID) {
time += route.time * 3600;
// plus 30 minutes transit time in each system
time += route.route.length * 1800;
} else {
var src = System.infoForSystem(galaxyNumber, itm.source);
route = src.routeToSystem(info, "OPTIMIZED_BY_TIME");
time += route.time * 3600;
// plus 15 minutes transit time in each system
time += route.route.length * 900;
}
break;
}
}
// bit of a buffer
time += 900;
} else {
if (itm.expiry > 0 && itm.percentComplete < 1) time = itm.expiry - clock.adjustedSeconds;
}
return time;
}
//-------------------------------------------------------------------------------------------------------------
// returns the mission details for a particular mission
this.$getItem = function $getItem(bbID) {
var checkval = parseInt(bbID);
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].ID === checkval) return this._data[i];
}
return null;
}
//-------------------------------------------------------------------------------------------------------------
// gets the data index of a particular BB item
// should not be used in most cases, as list can be resorted, making the index stale
this.$getIndex = function $getIndex(bbID) {
var checkval = parseInt(bbID);
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].ID === bbID) return i;
}
return -1;
}
//-------------------------------------------------------------------------------------------------------------
this.$addStationKey = function $addStationKey(missionWorldScript, stn, stationKey) {
var found = false;
for (var i = 0; i < this._stationKeys.length; i++) {
if (this._stationKeys[i].station === stn && this._stationKeys[i].worldScript === missionWorldScript && this._stationKeys[i].key === stationKey) {
found = true;
}
}
if (found === false) {
this._stationKeys.push({
station: stn,
worldScript: missionWorldScript,
key: stationKey
});
}
}
//-------------------------------------------------------------------------------------------------------------
// checks if the station/worldScript combination has any specific station keys added. Return true if found, otherwise false
this.$stationHasKeys = function $stationHasKeys(worldScript, stn) {
for (var i = 0; i < this._stationKeys.length; i++) {
if (this._stationKeys[i].station === stn && this._stationKeys[i].worldScript === worldScript) return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------------------
// works out the stationKey for the current station
this.$getStationKeyDefault = function $getStationKeyDefault(station) {
var stnKey = "";
// does the station have a particular station key set in script info?
if (station.scriptInfo.bb_station_key) stnKey = station.scriptInfo.bb_station_key;
// what about in the script object for the station? try there too...
if (stnKey === "" && station.script.bb_station_key) stnKey = station.script.bb_station_key;
// if not, does this station have an allegiance value set
if (stnKey === "" && station.allegiance != null) stnKey = station.allegiance;
return stnKey;
}
//-------------------------------------------------------------------------------------------------------------
// checks the current stationKey against mission's station keys. returns true if the current stationKey is one of the mission's station keys
// otherwise false
this.$checkMissionStationKey = function $checkMissionStationKey(missionWorldScript, station, missionStnKey) {
// a blank station key means anywhere
if (missionStnKey === "") return true;
var items = missionStnKey.split(",");
var def = this.$getStationKeyDefault(station);
var found = false;
var useDefault = (this.$stationHasKeys(missionWorldScript, station) ? false : true);
for (var i = 0; i < items.length; i++) {
if (useDefault === true) {
if (items[i] === def) found = true;
} else {
for (var j = 0; j < this._stationKeys.length; j++) {
if (this._stationKeys[j].station === station && items[i] === this._stationKeys[j].key && this._stationKeys[j].worldScript === missionWorldScript) found = true;
}
}
}
return found;
}
//-------------------------------------------------------------------------------------------------------------
// works out whether the bulletin board is hidden on this station
this.$stationIsAllowedBB = function $stationIsAllowedBB(station) {
var result = true;
if (station.scriptInfo && station.scriptInfo.bb_hide && station.scriptInfo.bb_hide === 1) result = false;
if (station.script && station.script.bb_hide && station.script.bb_hide === 1) result = false;
return result;
}
//-------------------------------------------------------------------------------------------------------------
this.$initInterface = function $initInterface(station) {
if (!station) return;
// get the station key for this station
this._stationKeyDefault = this.$getStationKeyDefault(station);
// count how many missions are available here
var avail = this.$countAvailable(station);
// count how many missions are active
var active = this.$countActive();
// create some additional text to add to the interface screen
var addtext = (avail > 0 ? avail + " available" : "") + (avail > 0 && active > 0 ? ", " : "") + (active > 0 ? active + " active" : "");
if (addtext != "") addtext = " (" + addtext + ")";
// work out the prefix for the interface
if ((addtext != "" || this._alwaysShowBB === true) && this.$stationIsAllowedBB(station) === true) {
var prefix = "Station";
if (station.allegiance === "galcop") prefix = "GalCop";
station.setInterface(this.name, {
title: prefix + " bulletin board" + addtext,
category: "Contracts",
summary: "Lists any local or specialised mission opportunities available at this station.",
callback: this.$showBB.bind(this)
});
} else {
station.setInterface(this.name, null);
}
}
//-------------------------------------------------------------------------------------------------------------
this.$showBB = function $showBB() {
this._lastChoice = ["", "", "", ""];
this._maxpage = Math.ceil(this.$countAvailable(player.ship.dockedStation) / this._msRows);
this._curpage = 0;
this._displayType = 0;
if (this._oldVersion != parseFloat(this.version)) this._displayType = 99;
this.$triggerBBEvent("open");
this._bbExiting = 1;
this._routeMode = "OPTIMIZED_BY_NONE";
if (player.ship.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
this._routeMode = player.ship.routeMode;
// if we have the array, by the current setting is "NONE", default to jumps
if (this._routeMode === "OPTIMIZED_BY_NONE") this._routeMode = "OPTIMIZED_BY_JUMPS";
}
this.$showPage();
}
//-------------------------------------------------------------------------------------------------------------
this.$showPage = function $showPage() {
function compareID(a, b) {
return ((a.ID > b.ID) ? 1 : -1);
}
function compareDate(a, b) {
return ((a.acceptedDate > b.acceptedDate) ? 1 : -1);
}
function comparePayment(a, b) {
return (((a.payment - a.deposit) < (b.payment - b.deposit)) ? 1 : -1);
}
var p = player.ship;
var stn = p.dockedStation;
if (this._displayType === -1) {
this._displayType = 0;
return;
}
//this._hudHidden = p.hudHidden;
if (this.$isBigGuiActive() === false) {
if (p.hud != "bbs_biggui_hud.plist") this._storeHUD = p.hud;
p.hud = "bbs_biggui_hud.plist";
}
this._bbOpen = true;
var text = "";
var opts;
var curChoices = {};
var def = "";
var iStart = 0;
var iEnd = 0;
var items = 0;
var flagCol = 1.0;
var jmpIndent = 6.45;
if (defaultFont.measureString("•") > 1) flagCol = defaultFont.measureString("•") + 0.1;
// help for new updates
if (this._displayType === 99) {
var update = false;
if (this._oldVersion === 1.4) {
update = true;
var ln = 0;
curChoices["0" + ln.toString() + "_A"] = {
text: "New feature for version 1.5: On/Near Path Notifications.",
alignment: "LEFT",
unselectable: true,
color: "whiteColor"
};
ln += 1;
curChoices[(ln <= 9 ? "0" : "") + ln.toString() + "_A"] = "";
ln += 1;
var lines = this.$columnText("Available or active mission items whose destination systems are directly on your currently plotted course will be coloured green on the mission listing. For example:", 32);
for (var i = 0; i < lines.length; i++) {
curChoices[(ln <= 9 ? "0" : "") + ln.toString() + "_A"] = {
text: lines[i],
unselectable: true,
alignment: "LEFT",
color: this._itemColor
};
ln += 1;
}
curChoices[(ln <= 9 ? "0" : "") + ln.toString() + "_A"] = {
text: this.$padTextRight(" ", flagCol) +
this.$padTextRight("Sample mission 1", 13) +
this.$padTextRight("Lave", 5) +
this.$padTextLeft("10 hrs", 5) +
this.$padTextLeft(formatCredits(100, false, true), 5) +
this.$padTextLeft("", 3),
alignment: "LEFT",
color: this._onPathColor,
unselectable: true
};
ln += 1;
curChoices[(ln <= 9 ? "0" : "") + ln.toString() + "_A"] = "";
ln += 1;
lines = this.$columnText("Available or active mission items whose destination systems are within 7ly of any system on your currently plotted course will be coloured dark green on the mission listing. For example:", 32);
for (var i = 0; i < lines.length; i++) {
curChoices[(ln <= 9 ? "0" : "") + ln.toString() + "_A"] = {
text: lines[i],
alignment: "LEFT",
unselectable: true,
color: this._itemColor
};
ln += 1;
}
curChoices[(ln <= 9 ? "0" : "") + ln.toString() + "_A"] = {
text: this.$padTextRight(" ", flagCol) +
this.$padTextRight("Sample mission 2", 13) +
this.$padTextRight("Tionisla", 5) +
this.$padTextLeft("15 hrs", 5) +
this.$padTextLeft(formatCredits(200, false, true), 5) +
this.$padTextLeft("", 3),
alignment: "LEFT",
color: this._nearPathColor,
unselectable: true
};
ln += 1;
curChoices[(ln <= 9 ? "0" : "") + ln.toString() + "_A"] = "";
ln += 1;
lines = this.$columnText("You can change the range for near systems, and also use markers to highlight these missions. Configuration options can be found in Library Config" + (worldScripts.Lib_Config ? "" : " (if Library.OXP is installed)") + ".", 32);
for (var i = 0; i < lines.length; i++) {
curChoices[(ln <= 9 ? "0" : "") + ln.toString() + "_A"] = {
text: lines[i],
alignment: "LEFT",
unselectable: true,
color: this._itemColor
};
ln += 1;
}
// spacers
for (var i = 0; i < (this._msRows + 5) - ln; i++) {
curChoices[(ln + i <= 9 ? "0" : "") + (ln + i).toString() + "_A"] = "";
}
this._oldVersion = 1.5;
}
if (update === true) {
curChoices["98_EXIT"] = {
text: "Press Enter to continue",
color: this._menuColor
};
var opts = {
screenID: "oolite-bbsystem-main-map",
title: "Bulletin Board - Update Info",
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES",
choices: curChoices,
initialChoicesKey: "98_EXIT",
message: text
};
if (this._overlay !== "") opts.overlay = this._overlay;
if (this._background !== "") opts.background = this._background;
} else {
// if there were no updates this time, switch back to the opening list
this._displayType = 0;
}
}
// main mission list
if (this._displayType === 0) {
this.$triggerBBEvent("preListDisplay");
if (this._data.length > 0) {
text = $padTextRight(" ", flagCol + 0.3) +
this.$padTextRight(expandDescription("[bb-header-description]"), 13) +
this.$padTextRight(expandDescription("[bb-header-destination]"), 5) +
this.$padTextLeft(expandDescription("[bb-header-expiry]"), 5) +
this.$padTextLeft(expandDescription("[bb-header-payment]"), 5) +
this.$padTextLeft("%", 3) + "\n\n";
var active = this.$countActive();
this._maxpage = Math.ceil((this.$countAvailable(stn) + (active > 0 ? 1 : 0) + active) / this._msRows);
if (this._maxpage === 0) this._maxpage = 1;
if (this._curpage > (this._maxpage - 1)) this._curpage = this._maxpage - 1;
this._data.sort(comparePayment);
var subdata = [];
var top = 0;
// add any missions available at this station
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].source === system.ID && this._data[i].accepted === false && (this._data[i].expiry === -1 || this.$isMissionExpired(this._data[i]) === false) && // this._data[i].expiry > clock.adjustedSeconds
(!this._data[i].stationKey || this._data[i].stationKey === "" || this.$checkMissionStationKey(this._data[i].worldScript, stn, this._data[i].stationKey) === true)) {
if (this.$isMissionAvailable(this._data[i]) === true) {
// available missions should go at the top of the list
subdata.splice(top, 0, this._data[i]);
top += 1;
} else {
// any missions that are unavailable for whatever reason are put at the bottom
subdata.push(this._data[i]);
}
}
}
// then put all the accepted missions at the top of the list
this._data.sort(compareDate);
top = 0;
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].accepted === true) {
subdata.splice(top, 0, this._data[i]);
top += 1;
}
}
if (top !== 0) {
// insert a dummy record to force a space between accepted and available missions
subdata.splice(top, 0, {
ID: -1
});
}
this._itemList.length = 0;
if (subdata.length > 0) {
// grab a copy of all the ID's of items in the list
for (var i = 0; i < subdata.length; i++) {
if (subdata[i].ID != -1) this._itemList.push(subdata[i].ID);
}
// set out initial end point
iStart = (this._curpage * this._msRows);
iEnd = iStart + this._msRows;
if (iEnd > subdata.length) iEnd = subdata.length;
for (var i = iStart; i < iEnd; i++) {
items += 1;
if (subdata[i].ID === -1) {
// add a spacer
curChoices["01_ITEM-" + (items < 10 ? "0" : "") + items + "~0"] = {
text: "",
alignment: "LEFT",
unselectable: true
};
} else {
// work out color of item
var colr = this._itemColor;
var onPath = this.$systemInCurrentPlot(subdata[i].destination);
var nearPath = (onPath === false ? this.$systemNearCurrentPlot(subdata[i].destination) : false);
if (this._useMarkers === 0 || this._useMarkers === 2) {
if (onPath === true) colr = this._onPathColor;
if (nearPath === true) colr = this._nearPathColor;
}
if (subdata[i].accepted === false && subdata[i].expiry > 0 && subdata[i].expiry < clock.adjustedSeconds) colr = this._warningColor;
if (this.$isMissionAvailable(subdata[i]) === false) colr = this._unavailableColor;
if (this.$isMissionCloseToExpiry(subdata[i]) === true) colr = this._menuColor;
curChoices["01_ITEM-" + (items < 10 ? "0" : "") + items + "~" + subdata[i].ID] = {
text: this.$padTextRight((subdata[i].accepted === true ? "•" : " "), flagCol) +
this.$padTextRight(subdata[i].description, 13) +
this.$padTextRight(subdata[i].destinationName + (this._useMarkers === 1 || this._useMarkers === 2 ? (onPath === true ? " †" : (nearPath === true ? " ‡" : "")) : ""), 5) +
this.$padTextLeft((subdata[i].percentComplete === 1 && subdata[i].stopTimeAtComplete === true ? " " : (subdata[i].expiry === -1 ? " " : this.$getTimeRemaining(subdata[i].expiry, true))), 5) +
this.$padTextLeft((subdata[i].payment > 0 ? formatCredits(subdata[i].payment - subdata[i].deposit, true, true) : ""), 5) +
this.$padTextLeft((subdata[i].accepted === true && (!subdata[i].disablePercentDisplay || subdata[i].disablePercentDisplay === false) ? (subdata[i].percentComplete * 100).toFixed(1) : ""), 3),
alignment: "LEFT",
color: colr
};
}
}
for (var i = 0; i < (((this._msRows + 1) - this._mainMenuItems.length) - items); i++) {
curChoices["02_SPACER_" + i] = "";
}
} else {
text += "No items to display";
}
if (this._mainMenuItems.length > 0) {
for (var i = 0; i < this._mainMenuItems.length; i++) {
var col = this._menuColor;
if (this._mainMenuItems[i].hasOwnProperty("color")) col = this._mainMenuItems[i].color;
var disabled = false;
if (this._mainMenuItems[i].hasOwnProperty("unselectable") && this._mainMenuItems[i].unselectable === true) {
disabled = true;
col = this._disabledColor;
}
curChoices["03_MENU~" + (i < 10 ? "0" : "") + i.toString()] = {
text: this._mainMenuItems[i].text,
color: col,
unselectable: disabled
};
}
}
if (this._curpage < this._maxpage - 1) {
curChoices["10_GOTONEXT"] = {
text: "[bb-nextpage]",
color: this._menuColor
};
} else {
curChoices["10_GOTONEXT"] = {
text: "[bb-nextpage]",
color: this._disabledColor,
unselectable: true
};
}
if (this._curpage > 0) {
curChoices["11_GOTOPREV"] = {
text: "[bb-prevpage]",
color: this._menuColor
};
} else {
curChoices["11_GOTOPREV"] = {
text: "[bb-prevpage]",
color: this._disabledColor,
unselectable: true
};
}
} else {
text += "\n" + expandDescription("[bb-no-items]");
this._maxpage = 1;
}
curChoices["99_EXIT"] = {
text: "Exit",
color: this._exitColor
};
var def = "99_EXIT";
if (this._lastChoice[this._displayType] != "") def = this._lastChoice[this._displayType];
var opts = {
screenID: "oolite-bbsystem-main-map",
title: "Bulletin Board - Page " + (this._curpage + 1) + " of " + this._maxpage,
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES",
choices: curChoices,
initialChoicesKey: def,
message: text
};
if (this._overlay !== "") opts.overlay = this._overlay;
if (this._background !== "") opts.background = this._background;
this.$triggerBBEvent("postListDisplay");
}
// mission details
if (this._displayType === 1) {
var govs = new Array();
for (var i = 0; i < 8; i++)
govs.push(String.fromCharCode(i));
var spc = String.fromCharCode(31);
var colWidth = 11;
var output = [];
var item = this.$getItem(this._selectedItem);
this.$triggerBBEvent("preItemDisplay", this._selectedItem);
// build up the array of output lines
// mission description
output.push(this.$padTextRight(expandDescription("[bb-item-description]"), colWidth) + item.description);
output.push("");
if (this._showID === true) {
output.push(this.$padTextRight("Mission ID:", colWidth) + item.ID);
}
// mission details
var coltext = [];
// allow for newline characters in the details text
var secthead = false;
var dtls = item.details.split("\n");
for (var j = 0; j < dtls.length; j++) {
coltext = this.$columnText(dtls[j], 32 - colWidth);
for (var i = 0; i < coltext.length; i++) {
if (secthead === false) {
output.push(this.$padTextRight(expandDescription("[bb-item-details]"), colWidth) + coltext[i]);
secthead = true;
} else {
output.push(this.$padTextRight(" ", colWidth) + coltext[i]);
}
}
}
output.push("");
var rt = null;
var dist = 0;
var time = 0;
var jumps = 0;
var expired = this.$isMissionExpired(item);
var sysID = system.ID;
if (system.ID === -1) sysID = p.targetSystem;
// source system info (only shown once a mission is accepted)
if (item.source != sysID || item.accepted === true) {
if (item.sourceGalaxy === galaxyNumber) {
var orig = System.infoForSystem(galaxyNumber, item.source);
var origtext = "";
rt = System.infoForSystem(galaxyNumber, sysID).routeToSystem(orig, this._routeMode);
if (rt) {
dist = rt.distance;
time = rt.time;
jumps = rt.route.length - 1;
if (this._routeMode === "OPTIMIZED_BY_NONE") {
origtext = orig.name + " (" + govs[orig.government] + spc + "TL" + (orig.techlevel + 1) +
(item.source === system.ID ? ", current system)" : ", dist: " + dist.toFixed(1) + "ly, " +
(jumps > 0 ? jumps + " jump" + (jumps > 1 ? "s" : "") + ", " : "") + time.toFixed(1) + " hrs)");
} else {
origtext = orig.name + " (" + govs[orig.government] + spc + "TL" + (orig.techlevel + 1) +
(item.source === system.ID ? ", current system)" : ", dist: " + dist.toFixed(1) + "ly)");
}
} else {
dist = System.infoForSystem(galaxyNumber, sysID).distanceToSystem(orig);
origtext = orig.name + " (" + govs[orig.government] + spc + "TL" + (orig.techlevel + 1) + ", dist:" + dist.toFixed(1) + "ly, unreachable)";
}
} else {
origtext = item.sourceName + " (G" + (item.sourceGalaxy + 1) + ", unreachable)";
}
secthead = false;
coltext = this.$columnText(origtext, 32 - colWidth);
for (var k = 0; k < coltext.length; k++) {
if (secthead === false) {
output.push(this.$padTextRight(expandDescription("[bb-item-originating]"), colWidth) + coltext[k]);
secthead = true;
} else {
output.push(this.$padTextRight(" ", colWidth) + coltext[k]);
}
}
}
// destination system info
var textitem = "";
if (item.destination < 256) {
if (item.destination >= 0 && item.destination <= 255) {
if (item.destinationGalaxy === galaxyNumber) {
var sys = System.infoForSystem(galaxyNumber, item.destination);
rt = System.infoForSystem(galaxyNumber, sysID).routeToSystem(sys, this._routeMode);
if (rt) {
dist = rt.distance;
time = rt.time;
jumps = rt.route.length - 1;
} else {
dist = -1;
time = -1;
jumps = -1;
}
if (dist >= 0) {
if (this._routeMode !== "OPTIMIZED_BY_NONE") {
textitem = sys.name + " (" + govs[sys.government] + spc + "TL" + (sys.techlevel + 1) +
(item.destination === system.ID ? ", current system)" : ", dist: " + dist.toFixed(1) + "ly, " +
(jumps > 0 ? jumps + " jump" + (jumps > 1 ? "s" : "") + ", " : "") + time.toFixed(1) + " hrs)");
} else {
// if we don't have the array, the distance is point-to-point, not route based.
if (p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY") === false) dist = system.info.distanceToSystem(sys);
textitem = sys.name + " (" + govs[sys.government] + spc + "TL" + (sys.techlevel + 1) +
(item.destination === system.ID ? ", current system)" : ", dist: " + dist.toFixed(1) + "ly, " + time.toFixed(1) + " hrs)");
}
} else {
dist = System.infoForSystem(galaxyNumber, sysID).distanceToSystem(sys);
textitem = sys.name + " (" + govs[sys.government] + spc + "TL" + (sys.techlevel + 1) + ", dist:" + dist.toFixed(1) + " ly, unreachable)";
}
} else {
textitem = item.destinationName + " (G" + (item.destinationGalaxy + 1) + ")";
}
} else if (item.destination === -1) {
textitem = "Interstellar space";
}
secthead = false;
coltext = this.$columnText(textitem, 32 - colWidth);
for (var k = 0; k < coltext.length; k++) {
if (secthead === false) {
output.push(this.$padTextRight(expandDescription("[bb-item-destination]"), colWidth) + coltext[k]);
secthead = true;
} else {
output.push(this.$padTextRight(" ", colWidth) + coltext[k]);
}
}
}
// expiry (if required)
if (item.expiry > 0 && (item.percentComplete < 1 || item.stopTimeAtComplete === false)) {
var closeExpiry = this.$isMissionCloseToExpiry(item);
var exp = this.$getTimeRemaining(item.expiry);
output.push(
this.$padTextRight(expandDescription("[bb-item-expiry]"), colWidth) +
exp +
(exp.indexOf("day") >= 0 ? " (" + this.$getTimeRemaining(item.expiry, true) + ")" : "") +
((closeExpiry === true && item.expiry > clock.adjustedSeconds) ? " **" : "")
);
if (closeExpiry === true) output.push(this.$padTextRight(" ", colWidth) + "** Close to expiry warning");
}
// payment amount
if (item.payment > 0) {
output.push(this.$padTextRight(expandDescription("[bb-item-payment]"), colWidth) + formatCredits(item.payment, true, true));
}
// bonus amount (if applicable)
if (expired === false && item.percentComplete === 1 && item.bonusCalculationCallback !== "") {
if (worldScripts[item.worldScript] && worldScripts[item.worldScript][itm.bonusCalculationCallback]) {
var bonus = worldScripts[item.worldScript][item.bonusCalculationCallback](item.ID);
output.push(this.$padTextRight(expandDescription("[bb-item-bonus]"), colWidth) + formatCredits(bonus, true, true));
}
}
// penalty amount (if required)
if (item.penalty > 0 && ((item.accepted === true && (item.percentComplete < 1 || expired === true)) || item.accepted === false)) {
output.push(this.$padTextRight(expandDescription("[bb-item-penalty]"), colWidth) + formatCredits(item.penalty * (1 - item.percentComplete), true, true));
}
// deposit amount (if supplied)
if (item.deposit && item.deposit > 0) {
output.push(this.$padTextRight(expandDescription("[bb-item-deposit]"), colWidth) + formatCredits(item.deposit, true, true));
output.push(this.$padTextRight(expandDescription("[bb-item-netpayment]"), colWidth) + formatCredits(item.payment - item.deposit, true, true));
}
// add any custom items (making sure we allow for long text items)
if (item.customDisplayItems && item.customDisplayItems != "") {
for (var i = 0; i < item.customDisplayItems.length; i++) {
secthead = false;
dtls = item.customDisplayItems[i].value.toString().split("\n");
if (dtls && dtls.length > 0) {
for (var j = 0; j < dtls.length; j++) {
coltext = this.$columnText(dtls[j], 32 - colWidth);
for (var k = 0; k < coltext.length; k++) {
if (secthead === false) {
output.push(this.$padTextRight(expandDescription(item.customDisplayItems[i].heading), colWidth) + coltext[k]);
secthead = true;
} else {
output.push(this.$padTextRight(" ", colWidth) + coltext[k]);
}
}
}
}
}
}
if (item.accepted === true) {
// percentage completed (if required)
if (!item.disablePercentDisplay || item.disablePercentDisplay === false) {
output.push(this.$padTextRight(expandDescription("[bb-item-percentcomplete]"), colWidth) + (item.percentComplete * 100).toFixed(1) + "%");
}
// any mission status text
if (item.manifestText != "" || item.statusText != "") { // item.percentComplete < 1 &&
if (item.statusText != "") {
coltext = this.$columnText(item.statusText, 32 - colWidth);
} else {
coltext = this.$columnText(item.manifestText, 32 - colWidth);
}
for (var i = 0; i < coltext.length; i++) {
if (i === 0) {
output.push(this.$padTextRight(expandDescription("[bb-item-status]"), colWidth) + coltext[i]);
} else {
output.push(this.$padTextRight(" ", colWidth) + coltext[i]);
}
}
}
}
var cmdCount = 0;
// add "Accept Mission"" item
if (item.accepted === false) {
var test = this.$missionUnavailableReason(item.ID);
if (test === "") {
curChoices["30_ACCEPT"] = {
text: "[bb-item-accept]",
color: this._menuColor
};
} else {
curChoices["30_ACCEPT"] = {
text: "Unavailable (" + test + ")",
color: this._disabledColor,
unselectable: true
};
}
cmdCount += 1;
}
var completeAvail = false;
if (item.accepted === true) {
// add "Terminate mission" item
if (item.percentComplete < 1 || expired === true) {
if (item.allowTerminate === true) {
curChoices["31_TERMINATE"] = {
text: "[bb-item-cancel]" + (item.penalty > 0 ? expandDescription("[bb-item-cancel-warning]") : ""),
color: this._menuColor
};
cmdCount += 1;
}
}
if (expired === false && (item.percentComplete === 1 || (item.allowPartialComplete && item.percentComplete > 0))) {
var result = "";
if (item.confirmCompleteCallback && item.confirmCompleteCallback !== "") {
if (worldScripts[item.worldScript] && worldScripts[item.worldScript][item.confirmCompleteCallback]) {
result = worldScripts[item.worldScript][item.confirmCompleteCallback](item.ID)
}
}
switch (item.completionType) {
case "AT_SOURCE":
// if the allowPartialComplete is on, the player should get a "Complete Mission" option, but only at the source station
// for any other station we want to hide the option completely
if (item.sourceGalaxy === galaxyNumber) {
if (item.source != system.ID) {
if (item.percentComplete === 1) {
curChoices["32_COMPLETE"] = {
text: expandDescription("[bb-item-complete]") + " (return to " + System.infoForSystem(galaxyNumber, item.source).name + ")",
color: this._disabledColor,
unselectable: true
};
} else if (this.$checkMissionStationKey(item.worldScript, stn, item.stationKey) === false && item.percentComplete === 1) {
curChoices["32_COMPLETE"] = {
text: expandDescription("[bb-item-complete]") + expandDescription("[bb-item-dock-original]"),
color: this._disabledColor,
unselectable: true
};
} else {
curChoices["32_COMPLETE"] = {
text: expandDescription("[bb-item-complete]") + (item.percentComplete < 1 ? expandDescription("[bb-item-partial]") : ""),
color: this._disabledColor,
unselectable: true
};
}
} else {
if (this.$checkMissionStationKey(item.worldScript, stn, item.stationKey) === false && item.percentComplete === 1) {
curChoices["32_COMPLETE"] = {
text: expandDescription("[bb-item-complete]") + expandDescription("[bb-item-dock-original]"),
color: this._disabledColor,
unselectable: true
};
} else {
curChoices["32_COMPLETE"] = {
text: expandDescription("[bb-item-complete]") + (result != "" ? " (" + result + ")" : (item.percentComplete < 1 ? expandDescription("[bb-item-partial]") : "")),
color: (result != "" ? this._disabledColor : this._menuColor),
unselectable: (result != "" ? true : false)
};
}
}
if (curChoices["32_COMPLETE"].unselectable === false) completeAvail = true;
cmdCount += 1;
}
break;
case "AT_STATIONKEY":
if (this.$checkMissionStationKey(item.worldScript, stn, item.stationKey) === false && item.percentComplete === 1) {
curChoices["32_COMPLETE"] = {
text: expandDescription("[bb-item-complete]") + expandDescription("[bb-item-dock-mainstation]"),
color: this._disabledColor,
unselectable: true
};
} else {
curChoices["32_COMPLETE"] = {
text: expandDescription("[bb-item-complete]") + (result != "" ? " (" + result + ")" : (item.percentComplete < 1 ? expandDescription("[bb-item-partial]") : "")),
color: (result != "" ? this._disabledColor : this._menuColor),
unselectable: (result != "" ? true : false)
};
}
if (curChoices["32_COMPLETE"].unselectable === false) completeAvail = true;
cmdCount += 1;
break;
case "ANYWHERE":
curChoices["32_COMPLETE"] = {
text: expandDescription("[bb-item-complete]") + (result != "" ? " (" + result + ")" : (item.percentComplete < 1 ? expandDescription("[bb-item-partial]") : "")),
color: (result != "" ? this._disabledColor : this._menuColor),
unselectable: (result != "" ? true : false)
};
if (curChoices["32_COMPLETE"].unselectable === false) completeAvail = true;
cmdCount += 1;
break;
}
}
}
// add "Show Map" item (if applicable)
if ((item.percentComplete < 1 || completeAvail === false) && item.destinationGalaxy === galaxyNumber && item.destination != system.ID && item.destination >= 0 && item.destination <= 255) {
if (item.hasOwnProperty("forceLongRangeChart") === true && item.forceLongRangeChart === true) {
curChoices["25_SHOWMAP_LONG"] = {
text: "[bb-item-showmap]",
color: this._menuColor
};
} else {
var dist = Math.round(system.info.distanceToSystem(sys), 2);
if (dist >= 50 || (dist > 7.2 && oolite.compareVersion("1.87") > 0)) {
curChoices["25_SHOWMAP_LONG"] = {
text: "[bb-item-showmap]",
color: this._menuColor
};
} else if (dist > 7.0 && oolite.compareVersion("1.87") <= 0) {
curChoices["25_SHOWMAP_CUSTOM"] = {
text: "[bb-item-showmap]",
color: this._menuColor
};
} else {
curChoices["25_SHOWMAP_SHORT"] = {
text: "[bb-item-showmap]",
color: this._menuColor
};
}
}
cmdCount += 1;
}
// add any "Set Course For" items (if applicable)
if ((item.percentComplete < 1 || completeAvail === false) && item.destination != system.ID &&
item.destinationGalaxy === galaxyNumber && item.destination >= 0 && item.destination <= 255 &&
this.$systemInCurrentPlot(item.destination) === false &&
p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
curChoices["96_SETCOURSE~" + item.destination] = {
text: "Set course for " + sys.name,
color: this._menuColor
};
cmdCount += 1;
}
// when complete, only show the "Set course" if the source isn't the current system
if (item.percentComplete === 1 && item.source != system.ID &&
item.sourceGalaxy === galaxyNumber && this.$systemInCurrentPlot(item.source) === false &&
(item.completionType == "AT_SOURCE" || item.completionType == "WHEN_DOCKED_SOURCE") &&
p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
curChoices["96_SETCOURSE~" + item.source] = {
text: "Set course for " + System.systemNameForID(item.source),
color: this._menuColor
};
cmdCount += 1;
}
// process any custom menu items
if (item.customMenuItems && item.customMenuItems != "") {
for (var i = 0; i < item.customMenuItems.length; i++) {
var mnu = item.customMenuItems[i];
var a_only = true;
var m_avail = "";
if (mnu.hasOwnProperty("activeOnly") === true && mnu.activeOnly === false) a_only = false;
if (item.accepted == a_only) {
if (mnu.hasOwnProperty("condition") === true) {
if (worldScripts[mnu.worldScript] && worldScripts[mnu.worldScript][mnu.condition]) {
m_avail = worldScripts[mnu.worldScript][mnu.condition](item.ID);
}
}
if (m_avail === "") {
curChoices["97_CUSTOM~" + i] = {
text: mnu.text,
color: this._menuColor
};
cmdCount += 1;
}
if (m_avail !== "") {
curChoices["97_CUSTOM~" + i] = {
text: mnu.text + " (" + m_avail + ")",
color: this._disabledColor,
unselectable: true
};
cmdCount += 1;
}
}
}
}
if (this._itemList.indexOf(this._selectedItem) === this._itemList.length - 1) {
curChoices["19_NEXTMISSION"] = {
text: "[bb-item-nextmission]",
color: this._disabledColor,
unselectable: true
};
} else {
curChoices["19_NEXTMISSION"] = {
text: "[bb-item-nextmission]",
color: this._menuColor
};
}
curChoices["98_EXIT"] = {
text: "[bb-item-close]",
color: this._exitColor
};
cmdCount += 1;
var start = 0;
var end = output.length - 1;
var extratext = "";
// check to see if we need to use paging
if (output.length > (27 - (cmdCount + 1))) {
// paging required
cmdCount += 2;
var pagelen = (27 - (cmdCount + 1));
var maxpage = Math.ceil(output.length / pagelen);
if (this._displayPage === 0 && this._displayPage < maxpage - 1) {
curChoices["21_NEXTPAGE"] = {
text: "[bb-nextpage]",
color: this._menuColor
};
} else {
curChoices["21_NEXTPAGE"] = {
text: "[bb-nextpage]",
color: this._disabledColor,
unselectable: true
};
}
if (this._displayPage > 0) {
curChoices["22_PREVPAGE"] = {
text: "[bb-prevpage]",
color: this._menuColor
};
} else {
curChoices["22_PREVPAGE"] = {
text: "[bb-prevpage]",
color: this._disabledColor,
unselectable: true
};
}
start = this._displayPage * pagelen;
end = this._displayPage * pagelen + pagelen - 1;
if (end > output.length - 1) end = output.length - 1;
extratext = " - Page " + (this._displayPage + 1) + " of " + maxpage;
}
// output the text lines
for (var i = start; i <= end; i++) {
text += output[i] + "\n";
}
var def = "98_EXIT";
if (this._lastChoice[this._displayType] != "") def = this._lastChoice[this._displayType];
var opts = {
screenID: "oolite-bbsystem-item-map",
title: expandDescription("[bb-item-heading]") + extratext,
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES",
choices: curChoices,
initialChoicesKey: def,
message: text
};
if (this._overlay !== "") opts.overlay = this._overlay;
if (item.overlay !== "") opts.overlay = item.overlay;
if (this._background !== "") opts.background = this._background;
if (item.background !== "") opts.background = item.background;
if (item.model != "") {
opts["model"] = item.model;
if (item.modelPersonality && item.modelPersonality != 0) opts["modelPersonality"] = item.modelPersonality;
if (item.spinModel && item.spinModel === false) opts["spinModel"] = false;
// if a model as been set, remove any overlay
delete opts["overlay"];
}
this.$triggerBBEvent("postItemDisplay", this._selectedItem);
}
// short range chart/custom chart
if (this._displayType === 2 || this._displayType === 3 || this._displayType === 7) {
// force the route mode to be the same as the player's current route mode
// this is because the short range chart doesn't have the option of switching between modes
//if (p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) this._routeMode = p.routeMode;
text = "";
var item = this.$getItem(this._selectedItem);
this.$triggerBBEvent("preItemChartDisplay", this._selectedItem);
var sys = System.infoForSystem(galaxyNumber, item.destination);
var screenPos = false;
if (item.destination != system.ID && p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY") && this._routeMode !== "OPTIMIZED_BY_NONE") {
var rt = System.infoForSystem(galaxyNumber, system.ID).routeToSystem(sys, this._routeMode);
if (rt && rt.route.length > 1) {
screenPos = true;
if (this._displayType === 3) {
text = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + this.$padTextRight("", jmpIndent) + expandDescription("[bb-item-jumps]") + " " + (rt.route.length - 1);
} else {
text = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + this.$padTextRight("", jmpIndent) + expandDescription("[bb-item-jumps]") + " " + (rt.route.length - 1);
}
}
}
var lines = "\n";
if (screenPos == false) {
if (this._displayType === 3) {
lines= "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
} else {
lines = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
}
}
text += lines + this.$padTextRight(item.description, 20) + this.$padTextLeft((item.payment > 0 ? "Payment: " + formatCredits(item.payment, true, true) : ""), 12);
// we need to get the current plot info before we switch the players destination, otherwise the check will always be true
var inCurrPlot_dest = this.$systemInCurrentPlot(item.destination);
var inCurrPlot_src = this.$systemInCurrentPlot(item.source);
// hold the player's destination
this._suspendedDestination = p.targetSystem;
// override it for the display
p.targetSystem = item.destination;
if (item.accepted === false) {
this.$addAdditionalMarkers(this._selectedItem);
this._tempMarkers = this._selectedItem;
var test = this.$missionUnavailableReason(item.ID);
if (test === "") {
curChoices["30_ACCEPT"] = {
text: "[bb-item-accept]",
color: this._menuColor
};
} else {
curChoices["30_ACCEPT"] = {
text: expandDescription("[bb-item-unavailable]") + " (" + test + ")",
color: this._disabledColor,
unselectable: true
};
}
}
var bg = "";
switch (this._displayType) {
case 2:
bg = "SHORT_RANGE_CHART";
break;
case 3:
bg = "LONG_RANGE_CHART";
break;
case 7:
bg = "CUSTOM_CHART";
break;
}
if (oolite.compareVersion("1.87") <= 0) {
if (p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
if (this._routeMode === "OPTIMIZED_BY_JUMPS") {
bg += "_SHORTEST";
curChoices["26_SHORTEST"] = {
text: "[bb-item-shortest]",
color: this._disabledColor,
unselectable: true
};
curChoices["27_QUICKEST"] = {
text: "[bb-item-quickest]",
color: this._menuColor
};
} else {
bg += "_QUICKEST";
curChoices["26_SHORTEST"] = {
text: "[bb-item-shortest]",
color: this._menuColor
};
curChoices["27_QUICKEST"] = {
text: "[bb-item-quickest]",
color: this._disabledColor,
unselectable: true
};
}
}
}
var result = "";
if (item.confirmCompleteCallback && item.confirmCompleteCallback !== "") {
if (worldScripts[item.worldScript] && worldScripts[item.worldScript][item.confirmCompleteCallback]) {
result = worldScripts[item.worldScript][item.confirmCompleteCallback](item.ID)
}
}
curChoices["20_RETURN"] = {
text: "[bb-item-return]",
color: this._menuColor
};
if ((item.percentComplete < 1 || result != "") && item.destination != system.ID && inCurrPlot_dest === false && p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
curChoices["96_SETCOURSE~" + item.destination] = {
text: "Set course for " + sys.name,
color: this._menuColor
};
}
if (item.percentComplete === 1 && item.source != system.ID && inCurrPlot_src === false && p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
curChoices["96_SETCOURSE~" + item.source] = {
text: "Set course for " + System.systemNameForID(item.source),
color: this._menuColor
};
cmdCount += 1;
}
if (this._nextContractOnMap === false) {
curChoices["97_CLOSE"] = {
text: "[bb-item-close]",
color: this._exitColor
};
def = "97_CLOSE";
} else {
if (this._itemList.indexOf(this._selectedItem) === this._itemList.length - 1) {
curChoices["19_NEXTMISSION"] = {
text: "[bb-item-nextmission]",
color: this._disabledColor,
unselectable: true
};
} else {
curChoices["19_NEXTMISSION"] = {
text: "[bb-item-nextmission]",
color: this._menuColor
};
}
def = "20_RETURN";
}
if (this._lastChoice[this._displayType] != "") def = this._lastChoice[this._displayType];
var opts = {
screenID: (this._displayType === 3 ? "oolite-bbsystem-longrangechart-map" : "oolite-bbsystem-shortrangechart-map"),
title: "Mission Details - Chart",
backgroundSpecial: bg,
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES",
choices: curChoices,
initialChoicesKey: def,
message: text
};
// a custom chart view, for missions with destinations between 7 and 50 LY away (Oolite 1.87 only)
if (this._displayType === 7) {
var dist = Math.round(system.info.distanceToSystem(sys), 2);
for (var i = 0; i < this._zoomDist.length; i++) {
if (dist >= this._zoomDist[i].dist) {
opts["customChartZoom"] = this._zoomDist[i].zoom;
break;
}
}
// calculate the midpoint between the source and destination, so we get the best view of the route
var point1 = system.info.coordinates;
var point2 = sys.coordinates;
var xdiff = (point1.x - point2.x) / 2;
var ydiff = (point1.y - point2.y) / 2;
opts["customChartCentreInLY"] = new Vector3D(point1.x - xdiff, point1.y - ydiff, 0);
}
if (this._overlay !== "") opts.overlay = this._overlay;
if (item.overlay !== "") opts.overlay = item.overlay;
if (item.mapOverlay !== "") opts.overlay = item.mapOverlay;
this.$triggerBBEvent("postItemChartDisplay", this._selectedItem);
}
// confirm terminate
if (this._displayType === 4) {
var item = this.$getItem(this._selectedItem);
text = expandDescription("[bb-confirm-terminate]");
curChoices["40_YES"] = {
text: "[bb-item-confirm-yes]",
color: this._menuColor
};
curChoices["41_NO"] = {
text: "[bb-item-confirm-no]",
color: this._menuColor
};
def = "41_NO";
var opts = {
screenID: "oolite-bbsystem-confirmterminate-map",
title: "Mission Terminate - Confirmation",
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES",
choices: curChoices,
initialChoicesKey: def,
message: text
};
if (this._overlay !== "") opts.overlay = this._overlay;
if (item.overlay !== "") opts.overlay = item.overlay;
if (this._background !== "") opts.background = this._background;
if (item.background !== "") opts.background = item.background;
if (item.model != "") {
opts["model"] = item.model;
if (item.modelPersonality && item.modelPersonality != 0) opts["modelPersonality"] = item.modelPersonality;
if (item.spinModel && item.spinModel === false) opts["spinModel"] = false;
// if a model as been set, remove any overlay
delete opts["overlay"];
}
}
// unable to complete mission screen result
if (this._displayType === 5) {
var item = this.$getItem(this._selectedItem);
text = expandDescription("[bb-unable-to-complete]") + "\n\n" + this._notCompleteText;
curChoices["97A_CLOSE"] = {
text: "[bb-item-close]",
color: this._menuColor
};
def = "97A_CLOSE";
var opts = {
screenID: "oolite-bbsystem-incomplete-map",
title: "Mission Details - Incomplete",
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES",
choices: curChoices,
initialChoicesKey: def,
message: text
};
if (this._overlay !== "") opts.overlay = this._overlay;
if (item.overlay !== "") opts.overlay = item.overlay;
if (this._background !== "") opts.background = this._background;
if (item.background !== "") opts.background = item.background;
if (item.model != "") {
opts["model"] = item.model;
if (item.modelPersonality != 0) opts["modelPersonality"] = item.modelPersonality;
if (item.spinModel === false) opts["spinModel"] = false;
// if a model as been set, remove any overlay
delete opts["overlay"];
}
}
// post messages
if (this._displayType === 10 || this._displayType === 11 || this._displayType === 12) {
var type = ["initiated", "completed", "terminated"];
var post = this._holdItem;
text = expandDescription(post.text);
if (!post.return || post.return === "list") {
curChoices["97_CLOSE"] = {
text: "[bb-item-close]",
color: this._menuColor
};
def = "97_CLOSE";
}
if (post.return && post.return === "item") {
curChoices["97A_CLOSE"] = {
text: "[bb-item-close]",
color: this._menuColor
};
def = "97A_CLOSE";
}
if (post.return && post.return === "exit") {
curChoices["99_EXIT"] = {
text: "[bb-item-close]",
color: this._menuColor
};
def = "99_EXIT";
}
var opts = {
screenID: "oolite-bbsystem-incomplete-map",
title: expandDescription("[bb-title-" + type[this._displayType - 10] + "]"),
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES",
choices: curChoices,
initialChoicesKey: def,
message: text
};
if (this._overlay !== "") opts.overlay = this._overlay;
if (post.overlay !== "") opts.overlay = post.overlay;
if (this._background !== "") opts.background = this._background;
if (post.background !== "") opts.background = post.background;
if (post.model && post.model != "") {
opts["model"] = post.model;
if (post.modelPersonality && post.modelPersonality != 0) opts["modelPersonality"] = post.modelPersonality;
if (post.spinModel && post.spinModel === false) opts["spinModel"] = false;
// if a model as been set, remove any overlay
delete opts["overlay"];
}
}
mission.runScreen(opts, this.$bbHandler, this);
}
//-------------------------------------------------------------------------------------------------------------
// handles player selections on the BB screen
this.$bbHandler = function $bbHandler(choice) {
if (this._suspendedDestination >= 0) player.ship.targetSystem = this._suspendedDestination;
this._suspendedDestination = -1;
if (this._tempMarkers >= 0) {
this.$removeChartMarker(this._tempMarkers);
}
if (!choice) {
this.$triggerBBEvent("exit");
return;
}
var newChoice = "";
this._lastChoice[this._displayType] = choice;
// selected bb item from main list
if (choice.indexOf("01_ITEM") >= 0) {
this._selectedItem = parseInt(choice.substring(choice.indexOf("~") + 1));
this._displayType = 1;
this._displayPage = 0;
// from screen 0 into screen 1, always default to the "Exit" option.
this._lastChoice[this._displayType] = "98_EXIT";
}
// user defined bb main menu item
if (choice.indexOf("03_MENU") >= 0) {
var idx = parseInt(choice.substring(choice.indexOf("~") + 1));
if (worldScripts[this._mainMenuItems[idx].worldScript] && worldScripts[this._mainMenuItems[idx].worldScript][this._mainMenuItems[idx].menuCallback]) {
worldScripts[this._mainMenuItems[idx].worldScript][this._mainMenuItems[idx].menuCallback]();
}
if (this._mainMenuItems[idx] && this._mainMenuItems[idx].hasOwnProperty("autoRemove") && this._mainMenuItems[idx].autoRemove === true) {
this._mainMenuItems.splice(idx, 1);
}
// if the function needs to display a mission page during it's callback,
// it should set BulletinBoardSystem._displayPage = -1 before it finishes
// that will jump the BB out of it's cycle
// then, to jump back in, set BulletinBoardSystem._displayPage to 0 and call BulletinBoardSystem.$showPage()
// that will return the BB back to the main contract listing page
}
// selected "set course to" from details page
if (choice.indexOf("96_SETCOURSE") >= 0) {
var dest = parseInt(choice.substring(choice.indexOf("~") + 1));
if (dest >= 0 && dest <= 255) {
player.ship.targetSystem = dest;
player.ship.infoSystem = player.ship.targetSystem;
player.consoleMessage("Course set for " + System.systemNameForID(dest));
}
}
if (choice.indexOf("97_CUSTOM") >= 0) {
var idx = parseInt(choice.substring(choice.indexOf("~") + 1));
var item = this.$getItem(this._selectedItem);
var mnu = item.customMenuItems[idx];
if (mnu.worldScript && mnu.worldScript != "" && mnu.callback && mnu.callback != "") {
if (worldScripts[mnu.worldScript] && worldScripts[mnu.worldScript][mnu.callback]) {
worldScripts[mnu.worldScript][mnu.callback](item.ID);
}
if (mnu.hasOwnProperty("autoRemove") && mnu.autoRemove === true) item.customMenuItems.splice(idx, 1);
}
newChoice = "98_EXIT";
}
// other choices
switch (choice) {
case "11_GOTOPREV":
this._curpage -= 1;
if (this._curpage === 0) newChoice = "10_GOTONEXT";
break;
case "10_GOTONEXT":
this._curpage += 1;
if (this._curpage === this._maxpage - 1) newChoice = "11_GOTOPREV";
break;
case "19_NEXTMISSION":
for (var i = 0; i < this._itemList.length; i++) {
if (this._itemList[i] === this._selectedItem) {
var target = i + 1;
if (target < this._itemList.length) {
this._displayType = 1;
this._displayPage = 0;
this._selectedItem = this._itemList[target];
this._lastChoice[0] = "99_EXIT";
}
break;
}
}
break;
case "21_NEXTPAGE":
this._displayPage += 1;
break;
case "22_PREVPAGE":
this._displayPage -= 1;
break;
case "25_SHOWMAP_SHORT":
this._displayType = 2;
break;
case "25_SHOWMAP_CUSTOM":
this._displayType = 7;
break;
case "25_SHOWMAP_LONG":
this._displayType = 3;
break;
case "26_SHORTEST":
this._routeMode = "OPTIMIZED_BY_JUMPS";
newChoice = "27_QUICKEST";
break;
case "27_QUICKEST":
this._routeMode = "OPTIMIZED_BY_TIME";
newChoice = "26_SHORTEST";
break;
case "30_ACCEPT":
var item = this.$getItem(this._selectedItem);
if (item.deposit && item.deposit > 0) {
if (player.credits < item.deposit) {
player.consoleMessage("Unable to accept mission. Insufficient credits to cover required deposit.", 5);
break;
} else {
if (item.hasOwnProperty("remoteDepositProcess") === false || item.remoteDepositProcess === false) {
player.credits -= item.deposit;
player.consoleMessage("Deposit amount of " + formatCredits(item.deposit, true, true) + " was deducted from your account.", 4);
}
}
}
if (!item.hasOwnProperty("playAcceptedSound") || item.playAcceptedSound === true) this.$playAcceptContractSound();
item.accepted = true;
item.acceptedDate = clock.adjustedSeconds;
if (item.initiateCallback !== "") {
if (worldScripts[item.worldScript] && worldScripts[item.worldScript][item.initiateCallback]) {
worldScripts[item.worldScript][item.initiateCallback](item.ID);
}
}
// add item to manifest screen
this.$addManifestEntry(item.ID);
// send an email (if installed)
this.$sendEmail(player.ship.dockedStation, "accepted", item.ID);
this.$initInterface(player.ship.dockedStation);
if (this.$postDisplayAvailable(item.ID, "initiated") === true) {
this._displayType = 10;
this.$storePostMessageDetails(item.ID, "initiated");
}
break;
case "31_TERMINATE":
this._displayType = 4;
break;
case "32_COMPLETE":
var item = this.$getItem(this._selectedItem);
var result = "";
if (item.confirmCompleteCallback && item.confirmCompleteCallback !== "") {
if (worldScripts[item.worldScript] && worldScripts[item.worldScript][item.confirmCompleteCallback]) {
result = worldScripts[item.worldScript][item.confirmCompleteCallback](item.ID)
}
}
if (result === "") {
this._displayType = 0;
if (this.$postDisplayAvailable(item.ID, "completed") === true) {
this._displayType = 11;
this.$storePostMessageDetails(item.ID, "completed");
}
this.$completeBBMission(item.ID);
} else {
this._notCompleteText = result;
this._displayType = 5;
}
break;
case "20_RETURN":
this._displayType = 1;
break;
case "40_YES":
this._displayType = 0;
if (this.$postDisplayAvailable(this._selectedItem, "terminated") === true) {
this._displayType = 12;
this.$storePostMessageDetails(this._selectedItem, "terminated");
}
this.$failedBBMission(this._selectedItem, true);
break;
case "41_NO":
this._displayType = 1;
break;
case "97_CLOSE":
this._displayType = 0;
break;
case "97A_CLOSE":
this._displayType = 1;
this._displayPage = 0;
break;
case "98_EXIT":
this._displayType = 0;
break;
}
if (newChoice != "") this._lastChoice[this._displayType] = newChoice;
if (choice != "99_EXIT") {
this.$showPage();
} else {
this._bbExiting = 2;
this.$triggerBBEvent("close");
}
}
//-------------------------------------------------------------------------------------------------------------
this.missionScreenEnded = function(screenID) {
if (this._storeHUD != "") player.ship.hud = this._storeHUD;
this._storeHUD = "";
}
//-------------------------------------------------------------------------------------------------------------
// returns true if a HUD with allowBigGUI is enabled, otherwise false
this.$isBigGuiActive = function $isBigGuiActive() {
if (oolite.compareVersion("1.83") <= 0) {
return player.ship.hudAllowsBigGui;
} else {
var bigGuiHUD = ["XenonHUD.plist", "coluber_hud_ch01-dock.plist"]; // until there is a property we can check, I'll be listing HUD's that have the allow_big_gui property set here
if (bigGuiHUD.indexOf(player.ship.hud) >= 0) {
return true;
} else {
return false;
}
}
}
//-------------------------------------------------------------------------------------------------------------
// appends space to currentText to the specified length in 'em'
this.$padTextRight = function $padTextRight(currentText, desiredLength, leftSwitch) {
if (currentText == null) currentText = "";
var hairSpace = String.fromCharCode(31);
var ellip = "…";
var currentLength = defaultFont.measureString(currentText);
var hairSpaceLength = defaultFont.measureString(hairSpace);
// calculate number needed to fill remaining length
var padsNeeded = Math.floor((desiredLength - currentLength) / hairSpaceLength);
if (padsNeeded < 1) {
// text is too long for column, so start pulling characters off
var tmp = currentText;
do {
tmp = tmp.substring(0, tmp.length - 2) + ellip;
if (tmp === ellip) break;
} while (defaultFont.measureString(tmp) > desiredLength);
currentLength = defaultFont.measureString(tmp);
padsNeeded = Math.floor((desiredLength - currentLength) / hairSpaceLength);
currentText = tmp;
}
// quick way of generating a repeated string of that number
if (!leftSwitch || leftSwitch === false) {
return currentText + new Array(padsNeeded).join(hairSpace);
} else {
return new Array(padsNeeded).join(hairSpace) + currentText;
}
}
//-------------------------------------------------------------------------------------------------------------
// appends space to currentText to the specified length in 'em'
this.$padTextLeft = function $padTextLeft(currentText, desiredLength) {
return this.$padTextRight(currentText, desiredLength, true);
}
//-------------------------------------------------------------------------------------------------------------
// arranges text into a array of strings with a particular column width
this.$columnText = function $columnText(originalText, columnWidth) {
var returnText = [];
if (defaultFont.measureString(originalText) > columnWidth) {
var hold = originalText;
do {
var newline = "";
var remain = "";
var point = hold.length;
do {
point = hold.lastIndexOf(" ", point - 1);
newline = hold.substring(0, point).trim();
remain = hold.substring(point + 1).trim();
} while (defaultFont.measureString(newline) > columnWidth);
returnText.push(newline);
if (remain != "") {
if (defaultFont.measureString(remain) <= columnWidth) {
returnText.push(remain);
hold = "";
} else {
hold = remain;
}
} else {
hold = "";
}
} while (hold != "");
} else {
returnText.push(originalText);
}
return returnText;
}
//-------------------------------------------------------------------------------------------------------------
// returns a string containing the days, hours, minutes (and possibly seconds) remaining until the expiry time is reached
this.$getTimeRemaining = function $getTimeRemaining(expiry, hoursOnly) {
var hrsOnly = (hoursOnly && hoursOnly === true ? true : false);
var diff = expiry - clock.adjustedSeconds;
var result = "";
if (diff > 0) {
var days = (hrsOnly === true ? 0 : Math.floor(diff / 86400));
var hours = Math.floor((diff - (days * 86400)) / 3600);
var mins = Math.floor((diff - (days * 86400 + hours * 3600)) / 60);
var secs = Math.floor(diff - (days * 86400) - (hours * 3600) - (mins * 60));
// special case - reduce 1 hour down to mins
if (days === 0 && hours === 1 && mins < 40) {
hours = 0;
mins += 60;
}
// special case - reduce 1 min down to secs
if (days === 0 && hours === 0 && mins === 1 && secs < 40) {
mins = 0;
secs += 60;
}
if (hrsOnly === true && mins > 30 && hours > 1) hours += 1;
if (days > 0) result += days + (days > 1 ? " days" : " day");
if (hours > 0) result += (result === "" ? "" : " ") + hours + (hours > 1 ? " hrs" : " hr");
if (hrsOnly === false || (hours === 0 && mins > 0)) {
if (mins > 0) result += (result === "" ? "" : " ") + mins + (mins > 1 ? " mins" : " min");
}
if (hrsOnly === false || (hours === 0 && mins === 0 && secs > 0)) {
if (hours === 0 && secs > 0) result += (result === "" ? "" : " ") + secs + (secs > 1 ? " secs" : " sec");
}
} else {
if (hrsOnly === false) {
result = "No time - mission time expired!";
} else {
result = "Expired";
}
}
return result;
}
//-------------------------------------------------------------------------------------------------------------
// adds a mark to the galactic chart when a new mission is accepted
// also forces the manifest details to be updated
this.$addManifestEntry = function $addManifestEntry(bbID) {
var item = this.$getItem(bbID);
if (!item) return;
if (item.destination >= 0 && item.destination <= 255) {
if (item.markerShape != "NONE")
mission.markSystem({
system: item.destination,
name: item.worldScript + "_" + bbID,
markerShape: item.markerShape,
markerColor: item.markerColor,
markerScale: item.markerScale
});
}
this.$addAdditionalMarkers(bbID);
// if we don't have any manifest text yet, tell the originator to populate it
if (item.manifestText === "" && item.manifestCallback) {
if (worldScripts[item.worldScript] && worldScripts[item.worldScript][item.manifestCallback]) {
worldScripts[item.worldScript][item.manifestCallback](item.ID);
}
}
this.$refreshManifest();
}
//-------------------------------------------------------------------------------------------------------------
this.$addAdditionalMarkers = function $addAdditionalMarkers(bbID) {
var item = this.$getItem(bbID);
if (item.hasOwnProperty("additionalMarkers") === true) {
for (var i = 0; i < item.additionalMarkers.length; i++) {
var ai = item.additionalMarkers[i];
mission.markSystem({
system: ai.system,
name: item.worldScript + "_" + bbID,
markerShape: ai.markerShape,
markerColor: ai.markerColor,
markerScale: ai.markerScale
});
}
}
}
//-------------------------------------------------------------------------------------------------------------
// refreshes the mission text on the manifest screen
this.$refreshManifest = function $refreshManifest() {
function compareDate(a, b) {
return ((a.acceptedDate > b.acceptedDate) ? 1 : -1);
}
this._data.sort(compareDate);
var textData = [];
textData.push(expandDescription("[bb-manifest-header]"));
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].accepted === true) {
if (this._data[i].manifestText != "") {
// make sure the mission text will fit on the display by breaking up the text into screen-width columns
var coltext = this.$columnText(this._data[i].manifestText, 30);
for (var j = 0; j < coltext.length; j++) {
textData.push((j === 0 ? "" : " ") + coltext[j]);
}
}
}
}
if (textData.length === 1) {
mission.setInstructions(null, this.name);
} else {
mission.setInstructions(textData, this.name);
}
}
//-------------------------------------------------------------------------------------------------------------
// reverts the chart marker back to the source location (for when a mission is complete and the player needs to return to the source for payment)
this.$revertChartMarker = function $revertChartMarker(bbID) {
var item = this.$getItem(bbID);
if (item.markerShape != "NONE") {
// remove the existing chart marker
this.$removeChartMarker(bbID);
// point it at the source system
mission.markSystem({
system: item.source,
name: item.worldScript + "_" + bbID,
markerShape: item.markerShape,
markerColor: item.markerColor,
markerScale: item.markerScale
});
}
}
//-------------------------------------------------------------------------------------------------------------
this.$removeChartMarker = function $removeChartMarker(bbID) {
var item = this.$getItem(bbID);
// remove the chart marker
if (item && (item.markerShape != "NONE" || (item.hasOwnProperty("additionalMarkers") === true && item.additionalMarkers.length > 0))) {
// we're cycling through every possible system in case the destination was updated mid-mission
for (var i = 0; i <= 255; i++) {
mission.unmarkSystem({
system: i,
name: item.worldScript + "_" + bbID
});
}
}
}
//-------------------------------------------------------------------------------------------------------------
// removes the manifest screen entry for a particular mission
this.$removeManifestEntry = function $removeManifestEntry(bbID) {
// remove the chart marker
this.$removeChartMarker(bbID);
var item = this.$getItem(bbID);
// make sure this item gets removed from the manifest display as well
if (item) item.manifestText = "";
this.$refreshManifest();
}
//-------------------------------------------------------------------------------------------------------------
// sends confirmation emails (if the email system is installed)
this.$sendEmail = function $sendEmail(station, eventType, missID, trueAmt) {
var email = worldScripts.EmailSystem;
// don't bother sending email if the system isn't installed
if (email == null) return;
var itm = this.$getItem(missID);
if (!itm || (itm.hasOwnProperty("noEmails") && itm.noEmails === true)) return;
var text = "";
var subj = "Confirmed: '" + itm.description + "'";
if (trueAmt == null) trueAmt = itm.payment;
switch (eventType) {
case "accepted":
text += "This is to confirm that you have agreed to the following mission: " + itm.manifestText;
text += "\n\nFull description of the mission:\n" + itm.details;
text += (itm.payment > 0 ? "\n\nThe agreed payment for successfully completing this mission is " + formatCredits(itm.payment, true, true) + "." : "");
if (itm.penalty > 0) text += "\n\nIf you are unable to complete the mission within the time period allowed, you will be penalised " + formatCredits(itm.penalty, true, true) + ".";
subj += " accepted";
break;
case "terminated":
text += "This is to confirm that you have terminated the following mission: " + itm.originalManifestText;
if (trueAmt > 0) {
text += "\n\nYou have be penalised " + formatCredits(trueAmt, true, true) + " for terminating the mission before completion.";
} else if (trueAmt < 0) {
text += "\n\nBecause you have partially completed the mission, you have been awarded " + formatCredits(trueAmt, true, true) + ", which has been credited to your account.";
}
subj += " terminated";
break;
case "success":
text += "This is to confirm that you have successfully completed the following mission: " + itm.originalManifestText;
if (trueAmt > 0) text += "\n\nThe agreed payment for successfully completing this mission was " + formatCredits(trueAmt, true, true) + ", which has been credited to your account.";
subj += " completed";
break;
case "fail":
text += "This is to confirm that you have failed to complete the following mission within the specified time period: " + itm.originalManifestText;
if (trueAmt > 0) {
text += "\n\nBecause of this, you have been penalised " + formatCredits(trueAmt, true, true) + ".";
} else if (trueAmt < 0) {
text += "\n\nHowever, because you have partially completed the mission, you have been awarded " + formatCredits(trueAmt, true, true) + ", which has been credited to your account.";
}
subj += " failed";
break;
}
// each station can have it's own BB admin name
var stnName = "default";
if (station == null) {
if (system != -1) stnName = system.mainStation.displayName;
} else {
stnName = station.displayName;
}
if (this._bbAdminName[stnName] == null || this._bbAdminName[stnName] === "") this.$setupRepName(stnName);
text += expandDescription("\n\n[name]\nBulletin Board Administration Authority", {
name: this._bbAdminName[stnName]
});
var emailID = email.$createEmail({
sender: "Bulletin Board Admin",
subject: subj,
date: global.clock.seconds,
message: text
});
if (emailID && emailID > 0) {
itm.lastEmailID = emailID;
}
}
//-------------------------------------------------------------------------------------------------------------
// sets up the admin authority user name for confirmation emails
this.$setupRepName = function $setupRepName(stationName) {
this._bbAdminName[stationName] = randomName() + " " + randomName();
}
//-------------------------------------------------------------------------------------------------------------
// returns true if the passes System ID is in the player's currently plotted course, otherwise false
this.$systemInCurrentPlot = function $systemInCurrentPlot(sysID) {
var result = false;
var target = player.ship.targetSystem;
if (oolite.compareVersion("1.81") < 0) {
// in 1.81 or greater, the target system could be more than 7 ly away. It becomes, essentially, the final destination.
// there could be multiple interim stop points between the current system and the target system.
// the only way to get this info is to recreate a route using the same logic as entered on the ANA, and pick item 1
// from the list. That should be the next destination in the list.
var myRoute = System.infoForSystem(galaxyNumber, global.system.ID).routeToSystem(System.infoForSystem(galaxyNumber, target), player.ship.routeMode);
if (myRoute) {
if (myRoute.route.indexOf(sysID) >= 0) result = true;
}
} else {
if (target === sysID) result = true;
}
return result;
}
//-------------------------------------------------------------------------------------------------------------
// returns true if the passes System ID is in the player's currently plotted course, otherwise false
this.$systemNearCurrentPlot = function $systemNearCurrentPlot(sysID) {
var result = false;
var target = player.ship.targetSystem;
if (oolite.compareVersion("1.81") < 0) {
// in 1.81 or greater, the target system could be more than 7 ly away. It becomes, essentially, the final destination.
// there could be multiple interim stop points between the current system and the target system.
// the only way to get this info is to recreate a route using the same logic as entered on the ANA, and pick item 1
// from the list. That should be the next destination in the list.
var myRoute = System.infoForSystem(galaxyNumber, global.system.ID).routeToSystem(System.infoForSystem(galaxyNumber, target), player.ship.routeMode);
if (myRoute) {
for (var i = 0; i < myRoute.route.length; i++) {
var rtSys = myRoute.route[i];
var sys = System.infoForSystem(galaxyNumber, rtSys).systemsInRange(this._nearPathRange);
for (var j = 0; j < sys.length; j++) {
if (sys[j].systemID === sysID) {
result = true;
break;
}
}
if (result === true) break;
}
}
} else {
var sys = System.infoForSystem(galaxyNumber, target).systemsInRange(this._nearPathRange);
for (var i = 0; i < sys.length; i++) {
if (sys[i].systemID === sysID) {
result = true;
break;
}
}
}
return result;
}
//-------------------------------------------------------------------------------------------------------------
// returns true if there is a post-display message available for a particular status, otherwise false
this.$postDisplayAvailable = function $postDisplayAvailable(bbID, type) {
var item = worldScripts.BulletinBoardSystem.$getItem(bbID);
var list = item.postStatusMessages;
var result = false;
if (list && list.length > 0) {
for (var i = 0; i < list.length; i++) {
if (list[i].status === type) result = true;
}
}
return result;
}
//-------------------------------------------------------------------------------------------------------------
// returns the post display dictionary item for a particular status
this.$getPostDisplay = function $getPostDisplay(bbItem, type) {
var list = bbItem.postStatusMessages;
var result = {};
if (list && list.length > 0) {
for (var i = 0; i < list.length; i++) {
if (list[i].status === type) result = list[i];
}
}
return result;
}
//-------------------------------------------------------------------------------------------------------------
// stores details of a particular post message dictionary
// this is so we can delete the BB item but still show the message to the player
this.$storePostMessageDetails = function $storePostMessageDetails(bbID, type) {
var item = this.$getItem(bbID);
var post = this.$getPostDisplay(item, type);
this._holdItem = {};
this._holdItem["text"] = post.text;
if (post.model) this._holdItem["model"] = post.model;
if (post.modelPersonality) this._holdItem["modelPersonality"] = post.modelPersonality;
if (post.spinModel) this._holdItem["spinModel"] = post.spinModel;
if (post.overlay) this._holdItem["overlay"] = post.overlay;
if (post.background) this._holdItem["background"] = post.background;
if (post.return) this._holdItem["return"] = post.return;
}
//-------------------------------------------------------------------------------------------------------------
// make sure all records have an accepted date value (used for sorting)
this.$addAcceptedDate = function $addAcceptedDate() {
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].hasOwnProperty("acceptedDate") === false) {
if (this._data[i].accepted === true) {
this._data[i].acceptedDate = clock.adjustedSeconds;
} else {
this._data[i].acceptedDate = 0;
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
this.$systemNameForID = function $systemNameForID(dest) {
if (dest === 256) return "";
return System.systemNameForID(dest);
}
//-------------------------------------------------------------------------------------------------------------
// adds additional data elements to records, if not present
this.$updateData = function $updateData() {
for (var i = 0; i < this._data.length; i++) {
if (this._data[i].hasOwnProperty("sourceGalaxy") === false) this._data[i].sourceGalaxy = galaxyNumber;
if (this._data[i].hasOwnProperty("destinationGalaxy") === false) this._data[i].destinationGalaxy = galaxyNumber;
if (this._data[i].hasOwnProperty("sourceName") === false) this._data[i].sourceName = System.systemNameForID(this._data[i].source);
if (this._data[i].hasOwnProperty("destinationName") === false) this._data[i].destinationName = this.$systemNameForID(this._data[i].destination);
}
}
//-------------------------------------------------------------------------------------------------------------
// look for any orphaned system marks and remove them
this.$dataCleanup = function $dataCleanup() {
var mk = mission.markedSystems;
for (var i = mk.length - 1; i >= 0; i--) {
if (mk[i].name.indexOf("GalCopBB_Missions") >= 0) {
// get the mission ID
var id = parseInt(mk[i].name.substring(mk[i].name.lastIndexOf("_") + 1));
if (this.$getItem(id) === null) {
mission.unmarkSystem({
system: mk[i].system,
name: mk[i].name
});
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
this.$playAcceptContractSound = function $playAcceptContractSound() {
var sound = new SoundSource;
sound.sound = "[contract-accepted]";
sound.play();
} |