From e1b8a23f1e0a40d187e3016fe77b3a17d5842e62 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sat, 2 Mar 2019 19:15:10 -0800 Subject: [PATCH 01/34] Started server code refactor --- src/Message/Message.ts | 32 ++ src/{Message.js => Message/MessageHelpers.js} | 41 +- src/Script/RunningScript.ts | 155 ++++++ src/Script/Script.ts | 104 ++++ src/{Script.js => Script/ScriptHelpers.js} | 187 +------ src/Server.js | 468 ------------------ src/Server/AllServers.ts | 111 +++++ src/Server/Server.ts | 303 ++++++++++++ src/Server/ServerHelpers.js | 126 +++++ src/{ => Server}/ServerPurchases.js | 0 src/{ => Server}/SpecialServerIps.js | 0 utils/IPAddress.js | 33 -- utils/IPAddress.ts | 25 + 13 files changed, 868 insertions(+), 717 deletions(-) create mode 100644 src/Message/Message.ts rename src/{Message.js => Message/MessageHelpers.js} (86%) create mode 100644 src/Script/RunningScript.ts create mode 100644 src/Script/Script.ts rename src/{Script.js => Script/ScriptHelpers.js} (85%) mode change 100755 => 100644 delete mode 100644 src/Server.js create mode 100644 src/Server/AllServers.ts create mode 100644 src/Server/Server.ts create mode 100644 src/Server/ServerHelpers.js rename src/{ => Server}/ServerPurchases.js (100%) rename src/{ => Server}/SpecialServerIps.js (100%) delete mode 100644 utils/IPAddress.js create mode 100644 utils/IPAddress.ts diff --git a/src/Message/Message.ts b/src/Message/Message.ts new file mode 100644 index 000000000..1c56ecd8b --- /dev/null +++ b/src/Message/Message.ts @@ -0,0 +1,32 @@ +import { Reviver, + Generic_toJSON, + Generic_fromJSON } from "../../utils/JSONReviver"; + +export class Message { + // Initializes a Message Object from a JSON save state + static fromJSON(value: any): Message { + return Generic_fromJSON(Message, value.data); + } + + // Name of Message file + filename: string = ""; + + // The text contains in the Message + msg: string = "": + + // Flag indicating whether this Message has been received by the player + recvd: boolean = false; + + constructor(filename="", msg="") { + this.filename = filename; + this.msg = msg; + this.recvd = false; + } + + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("Message", this); + } +} + +Reviver.constructors.Message = Message; diff --git a/src/Message.js b/src/Message/MessageHelpers.js similarity index 86% rename from src/Message.js rename to src/Message/MessageHelpers.js index 7ebee10fa..8fc056fb9 100644 --- a/src/Message.js +++ b/src/Message/MessageHelpers.js @@ -1,34 +1,15 @@ -import { Augmentatation } from "./Augmentation/Augmentation"; -import { Augmentations } from "./Augmentation/Augmentations"; -import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; -import { Programs } from "./Programs/Programs"; -import { inMission } from "./Missions"; -import { Player } from "./Player"; -import { redPillFlag } from "./RedPill"; -import { GetServerByHostname } from "./Server"; -import { Settings } from "./Settings/Settings"; +import { Message } from "./Message"; +import { Augmentatation } from "../Augmentation/Augmentation"; +import { Augmentations } from "../Augmentation/Augmentations"; +import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; +import { Programs } from "../Programs/Programs"; +import { inMission } from "../Missions"; +import { Player } from "../Player"; +import { redPillFlag } from "../RedPill"; +import { GetServerByHostname } from "../Server"; +import { Settings } from "../Settings/Settings"; import { dialogBoxCreate, - dialogBoxOpened} from "../utils/DialogBox"; -import {Reviver, Generic_toJSON, - Generic_fromJSON} from "../utils/JSONReviver"; - -/* Message.js */ -function Message(filename="", msg="") { - this.filename = filename; - this.msg = msg; - this.recvd = false; -} - -Message.prototype.toJSON = function() { - return Generic_toJSON("Message", this); -} - - -Message.fromJSON = function(value) { - return Generic_fromJSON(Message, value.data); -} - -Reviver.constructors.Message = Message; + dialogBoxOpened} from "../../utils/DialogBox"; //Sends message to player, including a pop up function sendMessage(msg, forced=false) { diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts new file mode 100644 index 000000000..c20e3e5f5 --- /dev/null +++ b/src/Script/RunningScript.ts @@ -0,0 +1,155 @@ +// Class representing a Script instance that is actively running. +// A Script can have multiple active instances +import { Script } from "./Script"; +import { IMap } from "../types"; +import { Generic_fromJSON, + Generic_toJSON, + Reviver } from "../../utils/JSONReviver"; + +export class RunningScript { + // Initializes a RunningScript Object from a JSON save state + static fromJSON(value: any): RunningScript { + return Generic_fromJSON(RunningScript, value.data); + } + + // Script arguments + args: any[] = []; + + // Holds a map of servers hacked, where server = key and the value for each + // server is an array of four numbers. The four numbers represent: + // [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] + // This data is used for offline progress + dataMap: IMap = {}; + + // Script filename + filename: string = ""; + + // This script's logs. An array of log entries + logs: string[] = []; + + // Flag indicating whether the logs have been updated since + // the last time the UI was updated + logUpd: boolean = false; + + // Total amount of hacking experience earned from this script when offline + offlineExpGained: number = 0; + + // Total amount of money made by this script when offline + offlineMoneyMade: number = 0; + + // Number of seconds that the script has been running offline + offlineRunningTime: number = 0.01; + + // Total amount of hacking experience earned from this script when online + onlineExpGained: number = 0; + + // Total amount of money made by this script when online + onlineMoneyMade: number = 0; + + // Number of seconds that this script has been running online + onlineRunningTime: number = 0.01; + + // How much RAM this script uses for ONE thread + ramUsage: number = 0; + + // IP of the server on which this script is running + server: string = ""; + + // Number of threads that this script is running with + threads: number = 1; + + constructor(script: Script | null = null, args: any[] = []) { + if (script == null) { return; } + this.filename = script.filename; + this.args = args; + + this.server = script.server; //IP Address only + this.ramUsage = script.ramUsage; + } + + RunningScript.prototype.getCode = function() { + const server = AllServers[this.server]; + if (server == null) { return ""; } + for (let i = 0; i < server.scripts.length; ++i) { + if (server.scripts[i].filename === this.filename) { + return server.scripts[i].code; + } + } + + return ""; + } + + RunningScript.prototype.getRamUsage = function() { + if (this.ramUsage != null && this.ramUsage > 0) { return this.ramUsage; } // Use cached value + + const server = AllServers[this.server]; + if (server == null) { return 0; } + for (let i = 0; i < server.scripts.length; ++i) { + if (server.scripts[i].filename === this.filename) { + // Cache the ram usage for the next call + this.ramUsage = server.scripts[i].ramUsage; + return this.ramUsage; + } + } + + + return 0; + } + + RunningScript.prototype.log = function(txt) { + if (this.logs.length > Settings.MaxLogCapacity) { + //Delete first element and add new log entry to the end. + //TODO Eventually it might be better to replace this with circular array + //to improve performance + this.logs.shift(); + } + let logEntry = txt; + if (FconfSettings.ENABLE_TIMESTAMPS) { + logEntry = "[" + getTimestamp() + "] " + logEntry; + } + this.logs.push(logEntry); + this.logUpd = true; + } + + RunningScript.prototype.displayLog = function() { + for (var i = 0; i < this.logs.length; ++i) { + post(this.logs[i]); + } + } + + RunningScript.prototype.clearLog = function() { + this.logs.length = 0; + } + + //Update the moneyStolen and numTimesHack maps when hacking + RunningScript.prototype.recordHack = function(serverIp, moneyGained, n=1) { + if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { + this.dataMap[serverIp] = [0, 0, 0, 0]; + } + this.dataMap[serverIp][0] += moneyGained; + this.dataMap[serverIp][1] += n; + } + + //Update the grow map when calling grow() + RunningScript.prototype.recordGrow = function(serverIp, n=1) { + if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { + this.dataMap[serverIp] = [0, 0, 0, 0]; + } + this.dataMap[serverIp][2] += n; + } + + //Update the weaken map when calling weaken() { + RunningScript.prototype.recordWeaken = function(serverIp, n=1) { + if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { + this.dataMap[serverIp] = [0, 0, 0, 0]; + } + this.dataMap[serverIp][3] += n; + } + + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("RunningScript", this); + } +} + +Reviver.constructors.RunningScript = RunningScript; diff --git a/src/Script/Script.ts b/src/Script/Script.ts new file mode 100644 index 000000000..fa64ed0d8 --- /dev/null +++ b/src/Script/Script.ts @@ -0,0 +1,104 @@ +// Class representing a script file +// This does NOT represent a script that is actively running and +// being evaluated. See RunningScript for that +import { Page, + routing } from "../ui/navigationTracking"; +import { setTimeoutRef } from "../utils/SetTimeoutRef"; +import { Generic_fromJSON, + Generic_toJSON, + Reviver } from "../../utils/JSONReviver"; +import { roundToTwo } from "../../utils/helpers/roundToTwo"; + +export class Script { + // Initializes a Script Object from a JSON save state + static fromJSON(value: any): Script { + return Generic_fromJSON(Script, value.data); + } + + // Code for this script + code: string = ""; + + // Filename for the script file + filename: string = ""; + + // The dynamic module generated for this script when it is run. + // This is only applicable for NetscriptJS + module: any = ""; + + // Amount of RAM this Script requres to run + ramUsage: number = 0; + + // IP of server that this script is on. + server: string = ""; + + + constructor(fn: string = "", code: string = "", server: string = "") { + this.filename = fn; + this.code = code; + this.ramUsage = 0; + this.server = server; // IP of server this script is on + this.module = ""; + if (this.code !== "") {this.updateRamUsage();} + }; + + download(): void { + const filename = this.filename + ".js"; + const file = new Blob([this.code], {type: 'text/plain'}); + if (window.navigator.msSaveOrOpenBlob) {// IE10+ + window.navigator.msSaveOrOpenBlob(file, filename); + } else { // Others + var a = document.createElement("a"), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeoutRef(function() { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); + } + } + + // Save a script FROM THE SCRIPT EDITOR + saveScript(): void { + if (routing.isOn(Page.ScriptEditor)) { + //Update code and filename + const code = getCurrentEditor().getCode(); + this.code = code.replace(/^\s+|\s+$/g, ''); + + const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement; + if (filenameElem == null) { + console.error(`Failed to get Script filename DOM element`); + return; + } + this.filename = filenameElem!.value; + + // Server + this.server = Player.currentServer; + + //Calculate/update ram usage, execution time, etc. + this.updateRamUsage(); + + this.module = ""; + } + } + + // Updates the script's RAM usage based on its code + async updateRamUsage(): void { + // TODO Commented this out because I think its unnecessary + // DOuble check/Test + // var codeCopy = this.code.repeat(1); + var res = await calculateRamUsage(this.code); + if (res !== -1) { + this.ramUsage = roundToTwo(res); + } + } + + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("Script", this); + } +} + +Reviver.constructors.Script = Script; diff --git a/src/Script.js b/src/Script/ScriptHelpers.js old mode 100755 new mode 100644 similarity index 85% rename from src/Script.js rename to src/Script/ScriptHelpers.js index 5733ab404..45a6ebb99 --- a/src/Script.js +++ b/src/Script/ScriptHelpers.js @@ -308,44 +308,6 @@ function checkValidFilename(filename) { return false; } -function Script(fn = "", code = "", server = "") { - this.filename = fn; - this.code = code; - this.ramUsage = 0; - this.server = server; //IP of server this script is on - this.module = ""; - if (this.code !== "") {this.updateRamUsage();} -}; - -//Get the script data from the Script Editor and save it to the object -Script.prototype.saveScript = function() { - if (routing.isOn(Page.ScriptEditor)) { - //Update code and filename - const code = getCurrentEditor().getCode(); - this.code = code.replace(/^\s+|\s+$/g, ''); - - var filename = document.getElementById("script-editor-filename").value; - this.filename = filename; - - //Server - this.server = Player.currentServer; - - //Calculate/update ram usage, execution time, etc. - this.updateRamUsage(); - - this.module = ""; - } -} - -//Updates how much RAM the script uses when it is running. -Script.prototype.updateRamUsage = async function() { - var codeCopy = this.code.repeat(1); - var res = await calculateRamUsage(codeCopy); - if (res !== -1) { - this.ramUsage = roundToTwo(res); - } -} - // These special strings are used to reference the presence of a given logical // construct within a user script. const specialReferenceIF = "__SPECIAL_referenceIf"; @@ -747,36 +709,6 @@ async function calculateRamUsage(codeCopy) { return ramUsage; } -Script.prototype.download = function() { - var filename = this.filename + ".js"; - var file = new Blob([this.code], {type: 'text/plain'}); - if (window.navigator.msSaveOrOpenBlob) {// IE10+ - window.navigator.msSaveOrOpenBlob(file, filename); - } else { // Others - var a = document.createElement("a"), - url = URL.createObjectURL(file); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - setTimeoutRef(function() { - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - }, 0); - } -} - -Script.prototype.toJSON = function() { - return Generic_toJSON("Script", this); -} - - -Script.fromJSON = function(value) { - return Generic_fromJSON(Script, value.data); -} - -Reviver.constructors.Script = Script; - //Called when the game is loaded. Loads all running scripts (from all servers) //into worker scripts so that they will start running function loadAllRunningScripts() { @@ -916,122 +848,5 @@ function findRunningScript(filename, args, server) { return null; } -function RunningScript(script, args) { - if (script == null || script == undefined) { return; } - this.filename = script.filename; - this.args = args; - this.server = script.server; //IP Address only - this.ramUsage = script.ramUsage; - - this.logs = []; //Script logging. Array of strings, with each element being a log entry - this.logUpd = false; - - //Stats to display on the Scripts menu, and used to determine offline progress - this.offlineRunningTime = 0.01; //Seconds - this.offlineMoneyMade = 0; - this.offlineExpGained = 0; - this.onlineRunningTime = 0.01; //Seconds - this.onlineMoneyMade = 0; - this.onlineExpGained = 0; - - this.threads = 1; - - // Holds a map of all servers, where server = key and the value for each - // server is an array of four numbers. The four numbers represent: - // [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] - // This data is used for offline progress - this.dataMap = {}; -} - -RunningScript.prototype.getCode = function() { - const server = AllServers[this.server]; - if (server == null) { return ""; } - for (let i = 0; i < server.scripts.length; ++i) { - if (server.scripts[i].filename === this.filename) { - return server.scripts[i].code; - } - } - - return ""; -} - -RunningScript.prototype.getRamUsage = function() { - if (this.ramUsage != null && this.ramUsage > 0) { return this.ramUsage; } // Use cached value - - const server = AllServers[this.server]; - if (server == null) { return 0; } - for (let i = 0; i < server.scripts.length; ++i) { - if (server.scripts[i].filename === this.filename) { - // Cache the ram usage for the next call - this.ramUsage = server.scripts[i].ramUsage; - return this.ramUsage; - } - } - - - return 0; -} - -RunningScript.prototype.log = function(txt) { - if (this.logs.length > Settings.MaxLogCapacity) { - //Delete first element and add new log entry to the end. - //TODO Eventually it might be better to replace this with circular array - //to improve performance - this.logs.shift(); - } - let logEntry = txt; - if (FconfSettings.ENABLE_TIMESTAMPS) { - logEntry = "[" + getTimestamp() + "] " + logEntry; - } - this.logs.push(logEntry); - this.logUpd = true; -} - -RunningScript.prototype.displayLog = function() { - for (var i = 0; i < this.logs.length; ++i) { - post(this.logs[i]); - } -} - -RunningScript.prototype.clearLog = function() { - this.logs.length = 0; -} - -//Update the moneyStolen and numTimesHack maps when hacking -RunningScript.prototype.recordHack = function(serverIp, moneyGained, n=1) { - if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { - this.dataMap[serverIp] = [0, 0, 0, 0]; - } - this.dataMap[serverIp][0] += moneyGained; - this.dataMap[serverIp][1] += n; -} - -//Update the grow map when calling grow() -RunningScript.prototype.recordGrow = function(serverIp, n=1) { - if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { - this.dataMap[serverIp] = [0, 0, 0, 0]; - } - this.dataMap[serverIp][2] += n; -} - -//Update the weaken map when calling weaken() { -RunningScript.prototype.recordWeaken = function(serverIp, n=1) { - if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { - this.dataMap[serverIp] = [0, 0, 0, 0]; - } - this.dataMap[serverIp][3] += n; -} - -RunningScript.prototype.toJSON = function() { - return Generic_toJSON("RunningScript", this); -} - - -RunningScript.fromJSON = function(value) { - return Generic_fromJSON(RunningScript, value.data); -} - -Reviver.constructors.RunningScript = RunningScript; - export {loadAllRunningScripts, findRunningScript, - RunningScript, Script, scriptEditorInit, isScriptFilename}; + scriptEditorInit, isScriptFilename}; diff --git a/src/Server.js b/src/Server.js deleted file mode 100644 index 6aa27b6f4..000000000 --- a/src/Server.js +++ /dev/null @@ -1,468 +0,0 @@ -import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; -import { CodingContract, - ContractTypes } from "./CodingContracts"; -import { CONSTANTS } from "./Constants"; -import { Script, - isScriptFilename } from "./Script"; -import { Player } from "./Player"; -import { Programs } from "./Programs/Programs"; -import { SpecialServerIps } from "./SpecialServerIps"; -import { TextFile } from "./TextFile"; -import { getRandomInt } from "../utils/helpers/getRandomInt"; -import { createRandomIp, - ipExists } from "../utils/IPAddress"; -import { serverMetadata } from "./data/servers"; -import { Reviver, - Generic_toJSON, - Generic_fromJSON} from "../utils/JSONReviver"; -import {isValidIPAddress} from "../utils/helpers/isValidIPAddress"; - -function Server(params={ip:createRandomIp(), hostname:""}) { - /* Properties */ - //Connection information - this.ip = params.ip ? params.ip : createRandomIp(); - - var hostname = params.hostname; - var i = 0; - var suffix = ""; - while (GetServerByHostname(hostname+suffix) != null) { - //Server already exists - suffix = "-" + i; - ++i; - } - this.hostname = hostname + suffix; - this.organizationName = params.organizationName != null ? params.organizationName : ""; - this.isConnectedTo = params.isConnectedTo != null ? params.isConnectedTo : false; - - //Access information - this.hasAdminRights = params.adminRights != null ? params.adminRights : false; - this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false; - this.manuallyHacked = false; //Flag that tracks whether or not the server has been hacked at least once - - //RAM, CPU speed and Scripts - this.maxRam = params.maxRam != null ? params.maxRam : 0; //GB - this.ramUsed = 0; - this.cpuCores = 1; //Max of 8, affects hacking times and Hacking Mission starting Cores - - this.scripts = []; - this.runningScripts = []; //Stores RunningScript objects - this.programs = []; - this.messages = []; - this.textFiles = []; - this.contracts = []; - this.dir = 0; //new Directory(this, null, ""); TODO - - /* Hacking information (only valid for "foreign" aka non-purchased servers) */ - this.requiredHackingSkill = params.requiredHackingSkill != null ? params.requiredHackingSkill : 1; - this.moneyAvailable = params.moneyAvailable != null ? params.moneyAvailable * BitNodeMultipliers.ServerStartingMoney : 0; - this.moneyMax = 25 * this.moneyAvailable * BitNodeMultipliers.ServerMaxMoney; - - //Hack Difficulty is synonymous with server security. Base Difficulty = Starting difficulty - this.hackDifficulty = params.hackDifficulty != null ? params.hackDifficulty * BitNodeMultipliers.ServerStartingSecurity : 1; - this.baseDifficulty = this.hackDifficulty; - this.minDifficulty = Math.max(1, Math.round(this.hackDifficulty / 3)); - this.serverGrowth = params.serverGrowth != null ? params.serverGrowth : 1; //Integer from 0 to 100. Affects money increase from grow() - - //The IP's of all servers reachable from this one (what shows up if you run scan/netstat) - // NOTE: Only contains IP and not the Server objects themselves - this.serversOnNetwork = []; - - //Port information, required for porthacking servers to get admin rights - this.numOpenPortsRequired = params.numOpenPortsRequired != null ? params.numOpenPortsRequired : 5; - this.sshPortOpen = false; //Port 22 - this.ftpPortOpen = false; //Port 21 - this.smtpPortOpen = false; //Port 25 - this.httpPortOpen = false; //Port 80 - this.sqlPortOpen = false; //Port 1433 - this.openPortCount = 0; -}; - -Server.prototype.setMaxRam = function(ram) { - this.maxRam = ram; -} - -//The serverOnNetwork array holds the IP of all the servers. This function -//returns the actual Server objects -Server.prototype.getServerOnNetwork = function(i) { - if (i > this.serversOnNetwork.length) { - console.log("Tried to get server on network that was out of range"); - return; - } - return AllServers[this.serversOnNetwork[i]]; -} - -//Given the name of the script, returns the corresponding -//script object on the server (if it exists) -Server.prototype.getScript = function(scriptName) { - for (var i = 0; i < this.scripts.length; i++) { - if (this.scripts[i].filename == scriptName) { - return this.scripts[i]; - } - } - return null; -} - -Server.prototype.capDifficulty = function() { - if (this.hackDifficulty < this.minDifficulty) {this.hackDifficulty = this.minDifficulty;} - if (this.hackDifficulty < 1) {this.hackDifficulty = 1;} - //Place some arbitrarily limit that realistically should never happen unless someone is - //screwing around with the game - if (this.hackDifficulty > 1000000) {this.hackDifficulty = 1000000;} -} - -//Strengthens a server's security level (difficulty) by the specified amount -Server.prototype.fortify = function(amt) { - this.hackDifficulty += amt; - this.capDifficulty(); -} - -Server.prototype.weaken = function(amt) { - this.hackDifficulty -= (amt * BitNodeMultipliers.ServerWeakenRate); - this.capDifficulty(); -} - -// Write to a script file -// Overwrites existing files. Creates new files if the script does not eixst -Server.prototype.writeToScriptFile = function(fn, code) { - var ret = {success: false, overwritten: false}; - if (!isScriptFilename(fn)) { return ret; } - - //Check if the script already exists, and overwrite it if it does - for (let i = 0; i < this.scripts.length; ++i) { - if (fn === this.scripts[i].filename) { - let script = this.scripts[i]; - script.code = code; - script.updateRamUsage(); - script.module = ""; - ret.overwritten = true; - ret.success = true; - return ret; - } - } - - //Otherwise, create a new script - var newScript = new Script(); - newScript.filename = fn; - newScript.code = code; - newScript.updateRamUsage(); - newScript.server = this.ip; - this.scripts.push(newScript); - ret.success = true; - return ret; -} - -// Write to a text file -// Overwrites existing files. Creates new files if the text file does not exist -Server.prototype.writeToTextFile = function(fn, txt) { - var ret = {success: false, overwritten: false}; - if (!fn.endsWith("txt")) { return ret; } - - //Check if the text file already exists, and overwrite if it does - for (let i = 0; i < this.textFiles.length; ++i) { - if (this.textFiles[i].fn === fn) { - ret.overwritten = true; - this.textFiles[i].text = txt; - ret.success = true; - return ret; - } - } - - //Otherwise create a new text file - var newFile = new TextFile(fn, txt); - this.textFiles.push(newFile); - ret.success = true; - return ret; -} - -Server.prototype.addContract = function(contract) { - this.contracts.push(contract); -} - -Server.prototype.removeContract = function(contract) { - if (contract instanceof CodingContract) { - this.contracts = this.contracts.filter((c) => { - return c.fn !== contract.fn; - }); - } else { - this.contracts = this.contracts.filter((c) => { - return c.fn !== contract; - }); - } -} - -Server.prototype.getContract = function(contractName) { - for (const contract of this.contracts) { - if (contract.fn === contractName) { - return contract; - } - } - return null; -} - -//Functions for loading and saving a Server -Server.prototype.toJSON = function() { - return Generic_toJSON("Server", this); -} - -Server.fromJSON = function(value) { - return Generic_fromJSON(Server, value.data); -} - -Reviver.constructors.Server = Server; - -export function initForeignServers() { - /* Create a randomized network for all the foreign servers */ - //Groupings for creating a randomized network - const networkLayers = []; - for (let i = 0; i < 15; i++) { - networkLayers.push([]); - } - - // Essentially any property that is of type 'number | IMinMaxRange' - const propertiesToPatternMatch = [ - "hackDifficulty", - "moneyAvailable", - "requiredHackingSkill", - "serverGrowth" - ]; - - const toNumber = (value) => { - switch (typeof value) { - case 'number': - return value; - case 'object': - return getRandomInt(value.min, value.max); - default: - throw Error(`Do not know how to convert the type '${typeof value}' to a number`); - } - } - - for (const metadata of serverMetadata) { - const serverParams = { - hostname: metadata.hostname, - ip: createRandomIp(), - numOpenPortsRequired: metadata.numOpenPortsRequired, - organizationName: metadata.organizationName - }; - - if (metadata.maxRamExponent !== undefined) { - serverParams.maxRam = Math.pow(2, toNumber(metadata.maxRamExponent)); - } - - for (const prop of propertiesToPatternMatch) { - if (metadata[prop] !== undefined) { - serverParams[prop] = toNumber(metadata[prop]); - } - } - - const server = new Server(serverParams); - for (const filename of (metadata.literature || [])) { - server.messages.push(filename); - } - - if (metadata.specialName !== undefined) { - SpecialServerIps.addIp(metadata.specialName, server.ip); - } - - AddToAllServers(server); - if (metadata.networkLayer !== undefined) { - networkLayers[toNumber(metadata.networkLayer) - 1].push(server); - } - } - - /* Create a randomized network for all the foreign servers */ - const linkComputers = (server1, server2) => { - server1.serversOnNetwork.push(server2.ip); - server2.serversOnNetwork.push(server1.ip); - }; - - const getRandomArrayItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; - - const linkNetworkLayers = (network1, selectServer) => { - for (const server of network1) { - linkComputers(server, selectServer()); - } - }; - - // Connect the first tier of servers to the player's home computer - linkNetworkLayers(networkLayers[0], () => Player.getHomeComputer()); - for (let i = 1; i < networkLayers.length; i++) { - linkNetworkLayers(networkLayers[i], () => getRandomArrayItem(networkLayers[i - 1])); - } -} - -// Returns the number of cycles needed to grow the specified server by the -// specified amount. 'growth' parameter is in decimal form, not percentage -export function numCycleForGrowth(server, growth) { - let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty; - if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) { - ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate; - } - - const serverGrowthPercentage = server.serverGrowth / 100; - - const cycles = Math.log(growth)/(Math.log(ajdGrowthRate)*Player.hacking_grow_mult*serverGrowthPercentage); - return cycles; -} - -//Applied server growth for a single server. Returns the percentage growth -export function processSingleServerGrowth(server, numCycles) { - //Server growth processed once every 450 game cycles - const numServerGrowthCycles = Math.max(Math.floor(numCycles / 450), 0); - - //Get adjusted growth rate, which accounts for server security - const growthRate = CONSTANTS.ServerBaseGrowthRate; - var adjGrowthRate = 1 + (growthRate - 1) / server.hackDifficulty; - if (adjGrowthRate > CONSTANTS.ServerMaxGrowthRate) {adjGrowthRate = CONSTANTS.ServerMaxGrowthRate;} - - //Calculate adjusted server growth rate based on parameters - const serverGrowthPercentage = server.serverGrowth / 100; - const numServerGrowthCyclesAdjusted = numServerGrowthCycles * serverGrowthPercentage * BitNodeMultipliers.ServerGrowthRate; - - //Apply serverGrowth for the calculated number of growth cycles - var serverGrowth = Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * Player.hacking_grow_mult); - if (serverGrowth < 1) { - console.log("WARN: serverGrowth calculated to be less than 1"); - serverGrowth = 1; - } - - const oldMoneyAvailable = server.moneyAvailable; - server.moneyAvailable *= serverGrowth; - - // in case of data corruption - if (server.moneyMax && isNaN(server.moneyAvailable)) { - server.moneyAvailable = server.moneyMax; - } - - // cap at max - if (server.moneyMax && server.moneyAvailable > server.moneyMax) { - server.moneyAvailable = server.moneyMax; - } - - // if there was any growth at all, increase security - if (oldMoneyAvailable !== server.moneyAvailable) { - //Growing increases server security twice as much as hacking - let usedCycles = numCycleForGrowth(server, server.moneyAvailable / oldMoneyAvailable); - usedCycles = Math.max(0, usedCycles); - server.fortify(2 * CONSTANTS.ServerFortifyAmount * Math.ceil(usedCycles)); - } - return server.moneyAvailable / oldMoneyAvailable; -} - -export function prestigeHomeComputer(homeComp) { - const hasBitflume = homeComp.programs.includes(Programs.BitFlume.name); - - homeComp.programs.length = 0; //Remove programs - homeComp.runningScripts = []; - homeComp.serversOnNetwork = []; - homeComp.isConnectedTo = true; - homeComp.ramUsed = 0; - homeComp.programs.push(Programs.NukeProgram.name); - if (hasBitflume) { homeComp.programs.push(Programs.BitFlume.name); } - - //Update RAM usage on all scripts - homeComp.scripts.forEach(function(script) { - script.updateRamUsage(); - }); - - homeComp.messages.length = 0; //Remove .lit and .msg files - homeComp.messages.push("hackers-starting-handbook.lit"); -} - -//List of all servers that exist in the game, indexed by their ip -let AllServers = {}; - -export function prestigeAllServers() { - for (var member in AllServers) { - delete AllServers[member]; - } - AllServers = {}; -} - -export function loadAllServers(saveString) { - AllServers = JSON.parse(saveString, Reviver); -} - -function SizeOfAllServers() { - var size = 0, key; - for (key in AllServers) { - if (AllServers.hasOwnProperty(key)) size++; - } - return size; -} - -//Add a server onto the map of all servers in the game -export function AddToAllServers(server) { - var serverIp = server.ip; - if (ipExists(serverIp)) { - console.log("IP of server that's being added: " + serverIp); - console.log("Hostname of the server thats being added: " + server.hostname); - console.log("The server that already has this IP is: " + AllServers[serverIp].hostname); - throw new Error("Error: Trying to add a server with an existing IP"); - return; - } - AllServers[serverIp] = server; -} - -//Returns server object with corresponding hostname -// Relatively slow, would rather not use this a lot -export function GetServerByHostname(hostname) { - for (var ip in AllServers) { - if (AllServers.hasOwnProperty(ip)) { - if (AllServers[ip].hostname == hostname) { - return AllServers[ip]; - } - } - } - return null; -} - -//Get server by IP or hostname. Returns null if invalid -export function getServer(s) { - if (!isValidIPAddress(s)) { - return GetServerByHostname(s); - } - if(AllServers[s] !== undefined) { - return AllServers[s]; - } - return null; -} - -//Debugging tool -function PrintAllServers() { - for (var ip in AllServers) { - if (AllServers.hasOwnProperty(ip)) { - console.log("Ip: " + ip + ", hostname: " + AllServers[ip].hostname); - } - } -} - -// Directory object (folders) -function Directory(server, parent, name) { - this.s = server; //Ref to server - this.p = parent; //Ref to parent directory - this.c = []; //Subdirs - this.n = name; - this.d = parent.d + 1; //We'll only have a maximum depth of 3 or something - this.scrs = []; //Holds references to the scripts in server.scripts - this.pgms = []; - this.msgs = []; -} - -Directory.prototype.createSubdir = function(name) { - var subdir = new Directory(this.s, this, name); - -} - -Directory.prototype.getPath = function(name) { - var res = []; - var i = this; - while (i !== null) { - res.unshift(i.n, "/"); - i = i.parent; - } - res.unshift("/"); - return res.join(""); -} - -export {Server, AllServers}; diff --git a/src/Server/AllServers.ts b/src/Server/AllServers.ts new file mode 100644 index 000000000..e04c33fbb --- /dev/null +++ b/src/Server/AllServers.ts @@ -0,0 +1,111 @@ +import { ipExists } from "../../utils/IPAddress"; + +// Map of all Servers that exist in the game +// Key (string) = IP +// Value = Server object +let AllServers = {}; + +// Saftely add a Server to the AllServers map +export function AddToAllServers(server) { + var serverIp = server.ip; + if (ipExists(serverIp)) { + console.log("IP of server that's being added: " + serverIp); + console.log("Hostname of the server thats being added: " + server.hostname); + console.log("The server that already has this IP is: " + AllServers[serverIp].hostname); + throw new Error("Error: Trying to add a server with an existing IP"); + return; + } + AllServers[serverIp] = server; +} + +export function initForeignServers() { + /* Create a randomized network for all the foreign servers */ + //Groupings for creating a randomized network + const networkLayers = []; + for (let i = 0; i < 15; i++) { + networkLayers.push([]); + } + + // Essentially any property that is of type 'number | IMinMaxRange' + const propertiesToPatternMatch = [ + "hackDifficulty", + "moneyAvailable", + "requiredHackingSkill", + "serverGrowth" + ]; + + const toNumber = (value) => { + switch (typeof value) { + case 'number': + return value; + case 'object': + return getRandomInt(value.min, value.max); + default: + throw Error(`Do not know how to convert the type '${typeof value}' to a number`); + } + } + + for (const metadata of serverMetadata) { + const serverParams = { + hostname: metadata.hostname, + ip: createRandomIp(), + numOpenPortsRequired: metadata.numOpenPortsRequired, + organizationName: metadata.organizationName + }; + + if (metadata.maxRamExponent !== undefined) { + serverParams.maxRam = Math.pow(2, toNumber(metadata.maxRamExponent)); + } + + for (const prop of propertiesToPatternMatch) { + if (metadata[prop] !== undefined) { + serverParams[prop] = toNumber(metadata[prop]); + } + } + + const server = new Server(serverParams); + for (const filename of (metadata.literature || [])) { + server.messages.push(filename); + } + + if (metadata.specialName !== undefined) { + SpecialServerIps.addIp(metadata.specialName, server.ip); + } + + AddToAllServers(server); + if (metadata.networkLayer !== undefined) { + networkLayers[toNumber(metadata.networkLayer) - 1].push(server); + } + } + + /* Create a randomized network for all the foreign servers */ + const linkComputers = (server1, server2) => { + server1.serversOnNetwork.push(server2.ip); + server2.serversOnNetwork.push(server1.ip); + }; + + const getRandomArrayItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; + + const linkNetworkLayers = (network1, selectServer) => { + for (const server of network1) { + linkComputers(server, selectServer()); + } + }; + + // Connect the first tier of servers to the player's home computer + linkNetworkLayers(networkLayers[0], () => Player.getHomeComputer()); + for (let i = 1; i < networkLayers.length; i++) { + linkNetworkLayers(networkLayers[i], () => getRandomArrayItem(networkLayers[i - 1])); + } +} + +export function prestigeAllServers() { + for (var member in AllServers) { + delete AllServers[member]; + } + AllServers = {}; +} + +export function loadAllServers(saveString) { + AllServers = JSON.parse(saveString, Reviver); +} diff --git a/src/Server/Server.ts b/src/Server/Server.ts new file mode 100644 index 000000000..b4d1f33dc --- /dev/null +++ b/src/Server/Server.ts @@ -0,0 +1,303 @@ +// Class representing a single generic Server +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import { CodingContract } from "../CodingContracts"; +import { Message } from "../Message/Message"; +import { RunningScript } from "../Script/RunningScript"; +import { Script } from "../Script/Script"; +import { TextFile } from "../TextFile"; + +import { createRandomIp } from "../../utils/IPAddress"; +import { Generic_fromJSON, + Generic_toJSON, + Reviver } from "../../utils/JSONReviver"; + +interface IConstructorParams { + adminRights?: boolean; + hackDifficulty?: number; + hostname: string; + ip?: string; + isConnectedTo?: boolean; + maxRam?: number; + moneyAvailable?: number; + numOpenPortsRequired?: number; + organizationName?: string; + purchasedByPlayer?: boolean; + requiredHackingSkill?: number; + serverGrowth?: number; +} + +export class Server { + // Initial server security level + // (i.e. security level when the server was created) + baseDifficulty: number = 1; + + // Coding Contract files on this server + contracts: CodingContract[] = []; + + // How many CPU cores this server has. Maximum of 8. + // Currently, this only affects hacking missions + cpuCores: number = 1; + + // Flag indicating whether the FTP port is open + ftpPortOpen: boolean = false; + + // Server Security Level + hackDifficulty: number = 1; + + // Flag indicating whether player has admin/root access to this server + hasAdminRights: boolean = false; + + // Hostname. Must be unique + hostname: string = ""; + + // Flag indicating whether HTTP Port is open + httpPortOpen: boolean = false; + + // IP Address. Must be unique + ip: string = ""; + + // Flag indicating whether player is curently connected to this server + isConnectedTo: boolean = false; + + // Flag indicating whether this server has been manually hacked (ie. + // hacked through Terminal) by the player + manuallyHacked: boolean = false; + + // RAM (GB) available on this server + maxRam: number = 0; + + // Message files AND Literature files on this Server + // For Literature files, this array contains only the filename (string) + // For Messages, it contains the actual Message object + // TODO Separate literature files into its own property + messages: (Message | string)[] = []; + + // Minimum server security level that this server can be weakened to + minDifficulty: number = 1; + + // How much money currently resides on the server and can be hacked + moneyAvailable: number = 0; + + // Maximum amount of money that this server can hold + moneyMax: number = 0; + + // Number of open ports required in order to gain admin/root access + numOpenPortsRequired: number = 5; + + // How many ports are currently opened on the server + openPortCount: number = 0; + + // Name of company/faction/etc. that this server belongs to. + // Optional, not applicable to all Servers + organizationName: string = ""; + + // Programs on this servers. Contains only the names of the programs + programs: string[] = []; + + // Flag indicating wehther this is a purchased server + purchasedByPlayer: boolean = false; + + // RAM (GB) used. i.e. unavailable RAM + ramUsed: number = 0; + + // Hacking level required to hack this server + requiredHackingSkill: number = 1; + + // RunningScript files on this server + runningScripts: RunningScript[] = []; + + // Script files on this Server + scripts: Script[] = []; + + // Parameter that affects how effectively this server's money can + // be increased using the grow() Netscript function + serverGrowth: number = 1; + + // Contains the IP Addresses of all servers that are immediately + // reachable from this one + serversOnNetwork: string[] = []; + + // Flag indicating whether SMTP Port is open + smtpPortOpen: boolean = false; + + // Flag indicating whether SQL Port is open + sqlPortOpen: boolean = false; + + // Flag indicating whether the SSH Port is open + sshPortOpen: boolean = false; + + // Text files on this server + textFiles: TextFile[] = []; + + constructor(params: IConstructorParams={hostname: "", ip: createRandomIp() }) { + /* Properties */ + //Connection information + this.ip = params.ip ? params.ip : createRandomIp(); + + var hostname = params.hostname; + var i = 0; + var suffix = ""; + while (GetServerByHostname(hostname+suffix) != null) { + //Server already exists + suffix = "-" + i; + ++i; + } + this.hostname = hostname + suffix; + this.organizationName = params.organizationName != null ? params.organizationName : ""; + this.isConnectedTo = params.isConnectedTo != null ? params.isConnectedTo : false; + + //Access information + this.hasAdminRights = params.adminRights != null ? params.adminRights : false; + this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false; + + //RAM, CPU speed and Scripts + this.maxRam = params.maxRam != null ? params.maxRam : 0; //GB + + /* Hacking information (only valid for "foreign" aka non-purchased servers) */ + this.requiredHackingSkill = params.requiredHackingSkill != null ? params.requiredHackingSkill : 1; + this.moneyAvailable = params.moneyAvailable != null ? params.moneyAvailable * BitNodeMultipliers.ServerStartingMoney : 0; + this.moneyMax = 25 * this.moneyAvailable * BitNodeMultipliers.ServerMaxMoney; + + //Hack Difficulty is synonymous with server security. Base Difficulty = Starting difficulty + this.hackDifficulty = params.hackDifficulty != null ? params.hackDifficulty * BitNodeMultipliers.ServerStartingSecurity : 1; + this.baseDifficulty = this.hackDifficulty; + this.minDifficulty = Math.max(1, Math.round(this.hackDifficulty / 3)); + this.serverGrowth = params.serverGrowth != null ? params.serverGrowth : 1; //Integer from 0 to 100. Affects money increase from grow() + + //Port information, required for porthacking servers to get admin rights + this.numOpenPortsRequired = params.numOpenPortsRequired != null ? params.numOpenPortsRequired : 5; + }; + + setMaxRam(ram: number): void { + this.maxRam = ram; + } + + //The serverOnNetwork array holds the IP of all the servers. This function + //returns the actual Server objects + Server.prototype.getServerOnNetwork = function(i) { + if (i > this.serversOnNetwork.length) { + console.log("Tried to get server on network that was out of range"); + return; + } + return AllServers[this.serversOnNetwork[i]]; + } + + //Given the name of the script, returns the corresponding + //script object on the server (if it exists) + Server.prototype.getScript = function(scriptName) { + for (var i = 0; i < this.scripts.length; i++) { + if (this.scripts[i].filename == scriptName) { + return this.scripts[i]; + } + } + return null; + } + + Server.prototype.capDifficulty = function() { + if (this.hackDifficulty < this.minDifficulty) {this.hackDifficulty = this.minDifficulty;} + if (this.hackDifficulty < 1) {this.hackDifficulty = 1;} + //Place some arbitrarily limit that realistically should never happen unless someone is + //screwing around with the game + if (this.hackDifficulty > 1000000) {this.hackDifficulty = 1000000;} + } + + //Strengthens a server's security level (difficulty) by the specified amount + Server.prototype.fortify = function(amt) { + this.hackDifficulty += amt; + this.capDifficulty(); + } + + Server.prototype.weaken = function(amt) { + this.hackDifficulty -= (amt * BitNodeMultipliers.ServerWeakenRate); + this.capDifficulty(); + } + + // Write to a script file + // Overwrites existing files. Creates new files if the script does not eixst + Server.prototype.writeToScriptFile = function(fn, code) { + var ret = {success: false, overwritten: false}; + if (!isScriptFilename(fn)) { return ret; } + + //Check if the script already exists, and overwrite it if it does + for (let i = 0; i < this.scripts.length; ++i) { + if (fn === this.scripts[i].filename) { + let script = this.scripts[i]; + script.code = code; + script.updateRamUsage(); + script.module = ""; + ret.overwritten = true; + ret.success = true; + return ret; + } + } + + //Otherwise, create a new script + var newScript = new Script(); + newScript.filename = fn; + newScript.code = code; + newScript.updateRamUsage(); + newScript.server = this.ip; + this.scripts.push(newScript); + ret.success = true; + return ret; + } + + // Write to a text file + // Overwrites existing files. Creates new files if the text file does not exist + Server.prototype.writeToTextFile = function(fn, txt) { + var ret = {success: false, overwritten: false}; + if (!fn.endsWith("txt")) { return ret; } + + //Check if the text file already exists, and overwrite if it does + for (let i = 0; i < this.textFiles.length; ++i) { + if (this.textFiles[i].fn === fn) { + ret.overwritten = true; + this.textFiles[i].text = txt; + ret.success = true; + return ret; + } + } + + //Otherwise create a new text file + var newFile = new TextFile(fn, txt); + this.textFiles.push(newFile); + ret.success = true; + return ret; + } + + Server.prototype.addContract = function(contract) { + this.contracts.push(contract); + } + + Server.prototype.removeContract = function(contract) { + if (contract instanceof CodingContract) { + this.contracts = this.contracts.filter((c) => { + return c.fn !== contract.fn; + }); + } else { + this.contracts = this.contracts.filter((c) => { + return c.fn !== contract; + }); + } + } + + Server.prototype.getContract = function(contractName) { + for (const contract of this.contracts) { + if (contract.fn === contractName) { + return contract; + } + } + return null; + } +} + +//Functions for loading and saving a Server +Server.prototype.toJSON = function() { + return Generic_toJSON("Server", this); +} + +Server.fromJSON = function(value) { + return Generic_fromJSON(Server, value.data); +} + +Reviver.constructors.Server = Server; diff --git a/src/Server/ServerHelpers.js b/src/Server/ServerHelpers.js new file mode 100644 index 000000000..0ab1183f5 --- /dev/null +++ b/src/Server/ServerHelpers.js @@ -0,0 +1,126 @@ +import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; +import { CodingContract, + ContractTypes } from "./CodingContracts"; +import { CONSTANTS } from "./Constants"; +import { Script, + isScriptFilename } from "./Script"; +import { Player } from "./Player"; +import { Programs } from "./Programs/Programs"; +import { SpecialServerIps } from "./SpecialServerIps"; +import { TextFile } from "./TextFile"; +import { getRandomInt } from "../utils/helpers/getRandomInt"; +import { serverMetadata } from "./data/servers"; +import { Reviver, + Generic_toJSON, + Generic_fromJSON} from "../utils/JSONReviver"; +import {isValidIPAddress} from "../utils/helpers/isValidIPAddress"; + +// Returns the number of cycles needed to grow the specified server by the +// specified amount. 'growth' parameter is in decimal form, not percentage +export function numCycleForGrowth(server, growth) { + let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty; + if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) { + ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate; + } + + const serverGrowthPercentage = server.serverGrowth / 100; + + const cycles = Math.log(growth)/(Math.log(ajdGrowthRate)*Player.hacking_grow_mult*serverGrowthPercentage); + return cycles; +} + +//Applied server growth for a single server. Returns the percentage growth +export function processSingleServerGrowth(server, numCycles) { + //Server growth processed once every 450 game cycles + const numServerGrowthCycles = Math.max(Math.floor(numCycles / 450), 0); + + //Get adjusted growth rate, which accounts for server security + const growthRate = CONSTANTS.ServerBaseGrowthRate; + var adjGrowthRate = 1 + (growthRate - 1) / server.hackDifficulty; + if (adjGrowthRate > CONSTANTS.ServerMaxGrowthRate) {adjGrowthRate = CONSTANTS.ServerMaxGrowthRate;} + + //Calculate adjusted server growth rate based on parameters + const serverGrowthPercentage = server.serverGrowth / 100; + const numServerGrowthCyclesAdjusted = numServerGrowthCycles * serverGrowthPercentage * BitNodeMultipliers.ServerGrowthRate; + + //Apply serverGrowth for the calculated number of growth cycles + var serverGrowth = Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * Player.hacking_grow_mult); + if (serverGrowth < 1) { + console.log("WARN: serverGrowth calculated to be less than 1"); + serverGrowth = 1; + } + + const oldMoneyAvailable = server.moneyAvailable; + server.moneyAvailable *= serverGrowth; + + // in case of data corruption + if (server.moneyMax && isNaN(server.moneyAvailable)) { + server.moneyAvailable = server.moneyMax; + } + + // cap at max + if (server.moneyMax && server.moneyAvailable > server.moneyMax) { + server.moneyAvailable = server.moneyMax; + } + + // if there was any growth at all, increase security + if (oldMoneyAvailable !== server.moneyAvailable) { + //Growing increases server security twice as much as hacking + let usedCycles = numCycleForGrowth(server, server.moneyAvailable / oldMoneyAvailable); + usedCycles = Math.max(0, usedCycles); + server.fortify(2 * CONSTANTS.ServerFortifyAmount * Math.ceil(usedCycles)); + } + return server.moneyAvailable / oldMoneyAvailable; +} + +export function prestigeHomeComputer(homeComp) { + const hasBitflume = homeComp.programs.includes(Programs.BitFlume.name); + + homeComp.programs.length = 0; //Remove programs + homeComp.runningScripts = []; + homeComp.serversOnNetwork = []; + homeComp.isConnectedTo = true; + homeComp.ramUsed = 0; + homeComp.programs.push(Programs.NukeProgram.name); + if (hasBitflume) { homeComp.programs.push(Programs.BitFlume.name); } + + //Update RAM usage on all scripts + homeComp.scripts.forEach(function(script) { + script.updateRamUsage(); + }); + + homeComp.messages.length = 0; //Remove .lit and .msg files + homeComp.messages.push("hackers-starting-handbook.lit"); +} + +function SizeOfAllServers() { + var size = 0, key; + for (key in AllServers) { + if (AllServers.hasOwnProperty(key)) size++; + } + return size; +} + +//Returns server object with corresponding hostname +// Relatively slow, would rather not use this a lot +export function GetServerByHostname(hostname) { + for (var ip in AllServers) { + if (AllServers.hasOwnProperty(ip)) { + if (AllServers[ip].hostname == hostname) { + return AllServers[ip]; + } + } + } + return null; +} + +//Get server by IP or hostname. Returns null if invalid +export function getServer(s) { + if (!isValidIPAddress(s)) { + return GetServerByHostname(s); + } + if(AllServers[s] !== undefined) { + return AllServers[s]; + } + return null; +} diff --git a/src/ServerPurchases.js b/src/Server/ServerPurchases.js similarity index 100% rename from src/ServerPurchases.js rename to src/Server/ServerPurchases.js diff --git a/src/SpecialServerIps.js b/src/Server/SpecialServerIps.js similarity index 100% rename from src/SpecialServerIps.js rename to src/Server/SpecialServerIps.js diff --git a/utils/IPAddress.js b/utils/IPAddress.js deleted file mode 100644 index 44fa97156..000000000 --- a/utils/IPAddress.js +++ /dev/null @@ -1,33 +0,0 @@ -import {AllServers} from "../src/Server"; -import {getRandomByte} from "./helpers/getRandomByte"; - -/* Functions to deal with manipulating IP addresses*/ - -//Generate a random IP address -//Will not return an IP address that already exists in the AllServers array -function createRandomIp() { - var ip = getRandomByte(99) + '.' + - getRandomByte(9) + '.' + - getRandomByte(9) + '.' + - getRandomByte(9); - - //If the Ip already exists, recurse to create a new one - if (ipExists(ip)) { - return createRandomIp(); - } - return ip; -} - -//Returns true if the IP already exists in one of the game's servers -function ipExists(ip) { - for (var property in AllServers) { - if (AllServers.hasOwnProperty(property)) { - if (property == ip) { - return true; - } - } - } - return false; -} - -export {createRandomIp, ipExists}; diff --git a/utils/IPAddress.ts b/utils/IPAddress.ts new file mode 100644 index 000000000..691f27505 --- /dev/null +++ b/utils/IPAddress.ts @@ -0,0 +1,25 @@ +import {AllServers} from "../src/Server"; +import {getRandomByte} from "./helpers/getRandomByte"; + +/* Functions to deal with manipulating IP addresses*/ + +//Generate a random IP address +//Will not return an IP address that already exists in the AllServers array +export function createRandomIp(): string { + const ip: string = getRandomByte(99) + '.' + + getRandomByte(9) + '.' + + getRandomByte(9) + '.' + + getRandomByte(9); + + // If the Ip already exists, recurse to create a new one + if (ipExists(ip)) { + return createRandomIp(); + } + + return ip; +} + +// Returns true if the IP already exists in one of the game's servers +export function ipExists(ip: string) { + return (AllServers[ip] != null); +} From 473f0f14475c40b829fc278dcd07bdd27a5da76f Mon Sep 17 00:00:00 2001 From: danielyxie Date: Mon, 4 Mar 2019 17:40:28 -0800 Subject: [PATCH 02/34] Refactored Server/Script/Files code to TypeScript --- src/ActiveScriptsUI.js | 4 +- src/Augmentation/AugmentationHelpers.js | 6 +- src/CodingContractGenerator.js | 4 +- src/DarkWeb/DarkWeb.js | 2 +- src/DevMenu.js | 2 +- src/{ => Fconf}/Fconf.js | 15 +- src/Fconf/FconfSettings.ts | 10 + src/Hacking.js | 2 +- src/Location.js | 9 +- src/Message/Message.ts | 2 +- src/Message/MessageHelpers.js | 2 +- src/NetscriptEvaluator.js | 8 +- src/NetscriptFunctions.js | 30 +- src/NetscriptWorker.js | 2 +- src/PersonObjects/IPlayer.ts | 1 + src/Player.js | 6 +- src/Prestige.js | 25 +- src/SaveObject.js | 15 +- src/Script/RamCalculations.d.ts | 1 + src/Script/RamCalculations.js | 409 +++++++++++++++ src/Script/RunningScript.ts | 34 +- src/Script/Script.ts | 10 +- src/Script/ScriptHelpers.js | 475 ++---------------- src/Script/ScriptHelpersTS.ts | 4 + src/Server/AllServers.ts | 49 +- src/Server/Server.ts | 72 +-- .../{ServerHelpers.js => ServerHelpers.ts} | 65 ++- src/Server/ServerPurchases.js | 20 +- src/Server/SpecialServerIps.js | 50 -- src/Server/SpecialServerIps.ts | 56 +++ src/{ => Server}/data/servers.ts | 2 + src/Terminal.js | 45 +- src/engine.js | 17 +- utils/IPAddress.ts | 4 +- 34 files changed, 763 insertions(+), 695 deletions(-) rename src/{ => Fconf}/Fconf.js (95%) create mode 100644 src/Fconf/FconfSettings.ts create mode 100644 src/Script/RamCalculations.d.ts create mode 100644 src/Script/RamCalculations.js create mode 100644 src/Script/ScriptHelpersTS.ts rename src/Server/{ServerHelpers.js => ServerHelpers.ts} (67%) delete mode 100644 src/Server/SpecialServerIps.js create mode 100644 src/Server/SpecialServerIps.ts rename src/{ => Server}/data/servers.ts (99%) diff --git a/src/ActiveScriptsUI.js b/src/ActiveScriptsUI.js index cb86c0b1a..1cdc66b26 100644 --- a/src/ActiveScriptsUI.js +++ b/src/ActiveScriptsUI.js @@ -1,7 +1,7 @@ import {workerScripts, killWorkerScript} from "./NetscriptWorker"; -import {Player} from "./Player"; -import {getServer} from "./Server"; +import { Player } from "./Player"; +import { getServer } from "./Server/ServerHelpers"; import {numeralWrapper} from "./ui/numeralFormat"; import {dialogBoxCreate} from "../utils/DialogBox"; import {createAccordionElement} from "../utils/uiHelpers/createAccordionElement"; diff --git a/src/Augmentation/AugmentationHelpers.js b/src/Augmentation/AugmentationHelpers.js index b1cd629df..af405e93e 100644 --- a/src/Augmentation/AugmentationHelpers.js +++ b/src/Augmentation/AugmentationHelpers.js @@ -12,9 +12,9 @@ import { addWorkerScript } from "../NetscriptWorker"; import { Player } from "../Player"; import { prestigeAugmentation } from "../Prestige"; import { saveObject } from "../SaveObject"; -import { Script, - RunningScript} from "../Script"; -import { Server } from "../Server"; +import { RunningScript } from "../Script/RunningScript"; +import { Script } from "../Script/Script"; +import { Server } from "../Server/Server"; import { OwnedAugmentationsOrderSetting } from "../Settings/SettingEnums"; import { Settings } from "../Settings/Settings"; diff --git a/src/CodingContractGenerator.js b/src/CodingContractGenerator.js index 8cf9fdda2..1538703bd 100644 --- a/src/CodingContractGenerator.js +++ b/src/CodingContractGenerator.js @@ -3,8 +3,8 @@ import { CodingContract, CodingContractTypes } from "./CodingContracts"; import { Factions } from "./Faction/Factions"; import { Player } from "./Player"; -import { GetServerByHostname, - AllServers } from "./Server"; +import { AllServers } from "./Server/Server"; +import { GetServerByHostname } from "./Server/ServerHelpers"; import { getRandomInt } from "../utils/helpers/getRandomInt"; diff --git a/src/DarkWeb/DarkWeb.js b/src/DarkWeb/DarkWeb.js index 394e31982..b8a5cdf89 100644 --- a/src/DarkWeb/DarkWeb.js +++ b/src/DarkWeb/DarkWeb.js @@ -1,7 +1,7 @@ import { DarkWebItems } from "./DarkWebItems"; import { Player } from "../Player"; -import { SpecialServerIps } from "../SpecialServerIps"; +import { SpecialServerIps } from "../Server/SpecialServerIps"; import { post } from "../ui/postToTerminal"; import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress"; diff --git a/src/DevMenu.js b/src/DevMenu.js index 03b72f108..95591491c 100644 --- a/src/DevMenu.js +++ b/src/DevMenu.js @@ -8,7 +8,7 @@ import { Company } from "./Company/Company"; import { Programs } from "./Programs/Programs"; import { Factions } from "./Faction/Factions"; import { Player } from "./Player"; -import { AllServers } from "./Server"; +import { AllServers } from "./Server/AllServers"; import { hackWorldDaemon } from "./RedPill"; import { StockMarket, SymbolToStockMap } from "./StockMarket/StockMarket"; diff --git a/src/Fconf.js b/src/Fconf/Fconf.js similarity index 95% rename from src/Fconf.js rename to src/Fconf/Fconf.js index 6f125c26c..f40bcc8ce 100644 --- a/src/Fconf.js +++ b/src/Fconf/Fconf.js @@ -1,16 +1,7 @@ -import {parse, Node} from "../utils/acorn"; -import {dialogBoxCreate} from "../utils/DialogBox"; +import { FconfSettings } from "./FconfSettings"; -var FconfSettings = { - ENABLE_BASH_HOTKEYS: false, - ENABLE_TIMESTAMPS: false, - MAIN_MENU_STYLE: "default", - THEME_BACKGROUND_COLOR: "#000000", - THEME_FONT_COLOR: "#66ff33", - THEME_HIGHLIGHT_COLOR: "#ffffff", - THEME_PROMPT_COLOR: "#f92672", - WRAP_INPUT: false, -} +import { parse, Node } from "../../utils/acorn"; +import { dialogBoxCreate } from "../../utils/DialogBox"; var FconfComments = { ENABLE_BASH_HOTKEYS: "Improved Bash emulation mode. Setting this to 1 enables several\n" + diff --git a/src/Fconf/FconfSettings.ts b/src/Fconf/FconfSettings.ts new file mode 100644 index 000000000..f1c08bc73 --- /dev/null +++ b/src/Fconf/FconfSettings.ts @@ -0,0 +1,10 @@ +export const FconfSettings = { + ENABLE_BASH_HOTKEYS: false, + ENABLE_TIMESTAMPS: false, + MAIN_MENU_STYLE: "default", + THEME_BACKGROUND_COLOR: "#000000", + THEME_FONT_COLOR: "#66ff33", + THEME_HIGHLIGHT_COLOR: "#ffffff", + THEME_PROMPT_COLOR: "#f92672", + WRAP_INPUT: false, +} diff --git a/src/Hacking.js b/src/Hacking.js index 452d38120..893ca3779 100644 --- a/src/Hacking.js +++ b/src/Hacking.js @@ -1,6 +1,6 @@ import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; import { Player } from "./Player"; -import { Server } from "./Server"; +import { Server } from "./Server/Server"; /** * Returns the chance the player has to successfully hack a server diff --git a/src/Location.js b/src/Location.js index b00adc353..42caaa0ba 100644 --- a/src/Location.js +++ b/src/Location.js @@ -11,13 +11,16 @@ import {beginInfiltration} from "./Infiltration"; import {hasBladeburnerSF} from "./NetscriptFunctions"; import {Locations} from "./Locations"; import {Player} from "./Player"; -import {Server, AllServers, AddToAllServers} from "./Server"; +import { AllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server"; +import { AddToAllServers } from "./Server/ServerHelpers"; import { getPurchaseServerCost, purchaseServer, - purchaseRamForHomeComputer} from "./ServerPurchases"; + purchaseRamForHomeComputer } from "./Server/ServerPurchases"; import {Settings} from "./Settings/Settings"; import { SourceFileFlags } from "./SourceFile/SourceFileFlags"; -import {SpecialServerNames, SpecialServerIps} from "./SpecialServerIps"; +import { SpecialServerNames, + SpecialServerIps } from "./Server/SpecialServerIps"; import {numeralWrapper} from "./ui/numeralFormat"; diff --git a/src/Message/Message.ts b/src/Message/Message.ts index 1c56ecd8b..90e24aa2c 100644 --- a/src/Message/Message.ts +++ b/src/Message/Message.ts @@ -12,7 +12,7 @@ export class Message { filename: string = ""; // The text contains in the Message - msg: string = "": + msg: string = ""; // Flag indicating whether this Message has been received by the player recvd: boolean = false; diff --git a/src/Message/MessageHelpers.js b/src/Message/MessageHelpers.js index 8fc056fb9..4845e6a30 100644 --- a/src/Message/MessageHelpers.js +++ b/src/Message/MessageHelpers.js @@ -6,7 +6,7 @@ import { Programs } from "../Programs/Programs"; import { inMission } from "../Missions"; import { Player } from "../Player"; import { redPillFlag } from "../RedPill"; -import { GetServerByHostname } from "../Server"; +import { GetServerByHostname } from "../Server/ServerHelpers"; import { Settings } from "../Settings/Settings"; import { dialogBoxCreate, dialogBoxOpened} from "../../utils/DialogBox"; diff --git a/src/NetscriptEvaluator.js b/src/NetscriptEvaluator.js index 8e144ac15..c301f2b76 100644 --- a/src/NetscriptEvaluator.js +++ b/src/NetscriptEvaluator.js @@ -3,10 +3,12 @@ import { CONSTANTS } from "./Constants"; import { Player } from "./Player"; import { Environment } from "./NetscriptEnvironment"; import { WorkerScript, addWorkerScript} from "./NetscriptWorker"; -import { Server, getServer} from "./Server"; +import { Server } from "./Server/Server"; +import { getServer } from "./Server/ServerHelpers"; import { Settings } from "./Settings/Settings"; -import { Script, findRunningScript, - RunningScript } from "./Script"; +import { RunningScript } from "./Script/RunningScript"; +import { Script } from "./Script/Script"; +import { findRunningScript } from "./Script/ScriptHelpers"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 9b7e6a56a..ddc833cf6 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -31,20 +31,26 @@ import { joinFaction, import { getCostOfNextHacknetNode, purchaseHacknet } from "./HacknetNode"; import {Locations} from "./Locations"; -import {Message, Messages} from "./Message"; +import { Message } from "./Message/Message"; +import { Messages } from "./Message/MessageHelpers"; import {inMission} from "./Missions"; import {Player} from "./Player"; import { Programs } from "./Programs/Programs"; -import {Script, findRunningScript, RunningScript, - isScriptFilename} from "./Script"; -import {Server, getServer, AddToAllServers, - AllServers, processSingleServerGrowth, - GetServerByHostname, numCycleForGrowth} from "./Server"; +import { Script } from "./Script/Script"; +import { findRunningScript } from "./Script/ScriptHelpers"; +import { isScriptFilename } from "./Script/ScriptHelpersTS"; +import { AllServers, + AddToAllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server"; +import { GetServerByHostname, + getServer, + numCycleForGrowth, + processSingleServerGrowth } from "./Server/ServerHelpers"; import { getPurchaseServerCost, getPurchaseServerLimit, - getPurchaseServerMaxRam } from "./ServerPurchases"; + getPurchaseServerMaxRam } from "./Server/ServerPurchases"; import {Settings} from "./Settings/Settings"; -import {SpecialServerIps} from "./SpecialServerIps"; +import {SpecialServerIps} from "./Server/SpecialServerIps"; import {Stock} from "./StockMarket/Stock"; import {StockMarket, StockSymbols, SymbolToStockMap, initStockMarket, initSymbolToStockMap, buyStock, @@ -305,9 +311,9 @@ function NetscriptFunctions(workerScript) { for (var i = 0; i < server.serversOnNetwork.length; i++) { var entry; if (hostnames) { - entry = server.getServerOnNetwork(i).hostname; + entry = getServerOnNetwork(server, i).hostname; } else { - entry = server.getServerOnNetwork(i).ip; + entry = getServerOnNetwork(server, i).ip; } if (entry == null) { continue; @@ -483,7 +489,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.env.stopFlag) {return Promise.reject(workerScript);} const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable; server.moneyAvailable += (1 * threads); //It can be grown even if it has no money - var growthPercentage = processSingleServerGrowth(server, 450 * threads); + var growthPercentage = processSingleServerGrowth(server, 450 * threads, Player); const moneyAfter = server.moneyAvailable; workerScript.scriptRef.recordGrow(server.ip, threads); var expGain = calculateHackingExpGain(server) * threads; @@ -512,7 +518,7 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, `Invalid growth argument passed into growthAnalyze: ${growth}. Must be numeric`); } - return numCycleForGrowth(server, Number(growth)); + return numCycleForGrowth(server, Number(growth), Player); }, weaken : function(ip) { if (workerScript.checkingRam) { diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 936dc82f6..e698166f8 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -11,7 +11,7 @@ import {evaluate, isScriptErrorMessage, import {NetscriptFunctions} from "./NetscriptFunctions"; import {executeJSScript} from "./NetscriptJSEvaluator"; import {NetscriptPort} from "./NetscriptPort"; -import {AllServers} from "./Server"; +import { AllServers } from "./Server/AllServers"; import {Settings} from "./Settings/Settings"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index 948b72e22..c41247a2e 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -20,6 +20,7 @@ export interface IPlayer { city: string; companyName: string; corporation: any; + currentServer: string; factions: string[]; hacknetNodes: any[]; hasWseAccount: boolean; diff --git a/src/Player.js b/src/Player.js index 25c19af5f..a95234937 100644 --- a/src/Player.js +++ b/src/Player.js @@ -24,9 +24,11 @@ import {Gang, resetGangs} from "./Gang"; import {Locations} from "./Locations"; import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions"; import { Sleeve } from "./PersonObjects/Sleeve/Sleeve"; -import {AllServers, Server, AddToAllServers} from "./Server"; +import { AllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server"; +import { AddToAllServers } from "./Server/ServerHelpers"; import {Settings} from "./Settings/Settings"; -import {SpecialServerIps, SpecialServerNames} from "./SpecialServerIps"; +import {SpecialServerIps, SpecialServerNames} from "./Server/SpecialServerIps"; import {SourceFiles, applySourceFile} from "./SourceFile"; import { SourceFileFlags } from "./SourceFile/SourceFileFlags"; import Decimal from "decimal.js"; diff --git a/src/Prestige.js b/src/Prestige.js index 3e46c03f1..1b3ba168d 100755 --- a/src/Prestige.js +++ b/src/Prestige.js @@ -16,20 +16,25 @@ import { Factions, import { joinFaction } from "./Faction/FactionHelpers"; import {deleteGangDisplayContent} from "./Gang"; import {Locations} from "./Location"; -import {initMessages, Messages, Message} from "./Message"; +import { initMessages, + Messages, + Message } from "./Message/MessageHelpers"; import {initSingularitySFFlags, hasWallStreetSF}from "./NetscriptFunctions"; import {WorkerScript, workerScripts, prestigeWorkerScripts} from "./NetscriptWorker"; import {Player} from "./Player"; -import {AllServers, AddToAllServers, - initForeignServers, Server, - prestigeAllServers, - prestigeHomeComputer} from "./Server"; +import { AllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server" +import { AddToAllServers, + initForeignServers, + prestigeAllServers, + prestigeHomeComputer } from "./Server/ServerHelpers"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; -import {SpecialServerIps, SpecialServerIpsMap, - prestigeSpecialServerIps, - SpecialServerNames} from "./SpecialServerIps"; +import { SpecialServerIps, + SpecialServerIpsMap, + prestigeSpecialServerIps, + SpecialServerNames} from "./Server/SpecialServerIps"; import {initStockMarket, initSymbolToStockMap, stockMarketContentCreated, setStockMarketContentCreated} from "./StockMarket/StockMarket"; @@ -89,7 +94,7 @@ function prestigeAugmentation() { } //Re-create foreign servers - initForeignServers(); + initForeignServers(Player.getHomeComputer()); //Darkweb is purchase-able document.getElementById("location-purchase-tor").setAttribute("class", "a-link-button"); @@ -194,7 +199,7 @@ function prestigeSourceFile() { prestigeHomeComputer(homeComp); //Re-create foreign servers - initForeignServers(); + initForeignServers(Player.getHomeComputer()); var srcFile1Owned = false; for (var i = 0; i < Player.sourceFiles.length; ++i) { diff --git a/src/SaveObject.js b/src/SaveObject.js index 5667b1efb..e59c0f777 100755 --- a/src/SaveObject.js +++ b/src/SaveObject.js @@ -7,15 +7,18 @@ import {Engine} from "./engine"; import { Factions, loadFactions } from "./Faction/Factions"; import { processPassiveFactionRepGain } from "./Faction/FactionHelpers"; -import {FconfSettings, loadFconf} from "./Fconf"; +import { loadFconf } from "./Fconf/Fconf"; +import { FconfSettings } from "./Fconf/FconfSettings"; import {loadAllGangs, AllGangs} from "./Gang"; import {processAllHacknetNodeEarnings} from "./HacknetNode"; -import {loadMessages, initMessages, Messages} from "./Message"; +import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers"; import {Player, loadPlayer} from "./Player"; -import {loadAllRunningScripts} from "./Script"; -import {AllServers, loadAllServers} from "./Server"; -import {Settings} from "./Settings/Settings"; -import {loadSpecialServerIps, SpecialServerIps} from "./SpecialServerIps"; +import { loadAllRunningScripts } from "./Script/ScriptHelpers"; +import { AllServers } from "./Server/AllServers"; +import { loadAllServers } from "./Server/ServerHelpers"; +import { Settings } from "./Settings/Settings"; +import { loadSpecialServerIps, + SpecialServerIps } from "./Server/SpecialServerIps"; import {loadStockMarket, StockMarket} from "./StockMarket/StockMarket"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; diff --git a/src/Script/RamCalculations.d.ts b/src/Script/RamCalculations.d.ts new file mode 100644 index 000000000..0a886c842 --- /dev/null +++ b/src/Script/RamCalculations.d.ts @@ -0,0 +1 @@ +export declare function calculateRamUsage(codeCopy: string): number; diff --git a/src/Script/RamCalculations.js b/src/Script/RamCalculations.js new file mode 100644 index 000000000..643845a8e --- /dev/null +++ b/src/Script/RamCalculations.js @@ -0,0 +1,409 @@ +// Calculate a script's RAM usage +const walk = require("acorn/dist/walk"); // Importing this doesn't work for some reason. + +import { CONSTANTS } from "../Constants"; +import {evaluateImport} from "../NetscriptEvaluator"; +import { WorkerScript } from "../NetscriptWorker"; +import { Player } from "../Player"; +import {parse, Node} from "../../utils/acorn"; + +// These special strings are used to reference the presence of a given logical +// construct within a user script. +const specialReferenceIF = "__SPECIAL_referenceIf"; +const specialReferenceFOR = "__SPECIAL_referenceFor"; +const specialReferenceWHILE = "__SPECIAL_referenceWhile"; + +// The global scope of a script is registered under this key during parsing. +const memCheckGlobalKey = ".__GLOBAL__"; + +// Calcluates the amount of RAM a script uses. Uses parsing and AST walking only, +// rather than NetscriptEvaluator. This is useful because NetscriptJS code does +// not work under NetscriptEvaluator. +async function parseOnlyRamCalculate(server, code, workerScript) { + try { + // Maps dependent identifiers to their dependencies. + // + // The initial identifier is __SPECIAL_INITIAL_MODULE__.__GLOBAL__. + // It depends on all the functions declared in the module, all the global scopes + // of its imports, and any identifiers referenced in this global scope. Each + // function depends on all the identifiers referenced internally. + // We walk the dependency graph to calculate RAM usage, given that some identifiers + // reference Netscript functions which have a RAM cost. + let dependencyMap = {}; + + // Scripts we've parsed. + const completedParses = new Set(); + + // Scripts we've discovered that need to be parsed. + const parseQueue = []; + + // Parses a chunk of code with a given module name, and updates parseQueue and dependencyMap. + function parseCode(code, moduleName) { + const result = parseOnlyCalculateDeps(code, moduleName); + completedParses.add(moduleName); + + // Add any additional modules to the parse queue; + for (let i = 0; i < result.additionalModules.length; ++i) { + if (!completedParses.has(result.additionalModules[i])) { + parseQueue.push(result.additionalModules[i]); + } + } + + // Splice all the references in. + //Spread syntax not supported in edge, use Object.assign instead + //dependencyMap = {...dependencyMap, ...result.dependencyMap}; + dependencyMap = Object.assign(dependencyMap, result.dependencyMap); + } + + const initialModule = "__SPECIAL_INITIAL_MODULE__"; + parseCode(code, initialModule); + + while (parseQueue.length > 0) { + // Get the code from the server. + const nextModule = parseQueue.shift(); + + let code; + if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) { + try { + const module = await eval('import(nextModule)'); + code = ""; + for (const prop in module) { + if (typeof module[prop] === 'function') { + code += module[prop].toString() + ";\n"; + } + } + } catch(e) { + console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`); + return -1; + } + } else { + const script = server.getScript(nextModule.startsWith("./") ? nextModule.slice(2) : nextModule); + if (!script) { + console.warn("Invalid script"); + return -1; // No such script on the server. + } + code = script.code; + } + + parseCode(code, nextModule); + } + + // Finally, walk the reference map and generate a ram cost. The initial set of keys to scan + // are those that start with __SPECIAL_INITIAL_MODULE__. + let ram = CONSTANTS.ScriptBaseRamCost; + const unresolvedRefs = Object.keys(dependencyMap).filter(s => s.startsWith(initialModule)); + const resolvedRefs = new Set(); + while (unresolvedRefs.length > 0) { + const ref = unresolvedRefs.shift(); + + // Check if this is one of the special keys, and add the appropriate ram cost if so. + if (ref === "hacknet" && !resolvedRefs.has("hacknet")) { + ram += CONSTANTS.ScriptHacknetNodesRamCost; + } + if (ref === "document" && !resolvedRefs.has("document")) { + ram += CONSTANTS.ScriptDomRamCost; + } + if (ref === "window" && !resolvedRefs.has("window")) { + ram += CONSTANTS.ScriptDomRamCost; + } + + resolvedRefs.add(ref); + + if (ref.endsWith(".*")) { + // A prefix reference. We need to find all matching identifiers. + const prefix = ref.slice(0, ref.length - 2); + for (let ident of Object.keys(dependencyMap).filter(k => k.startsWith(prefix))) { + for (let dep of dependencyMap[ident] || []) { + if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); + } + } + } else { + // An exact reference. Add all dependencies of this ref. + for (let dep of dependencyMap[ref] || []) { + if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); + } + } + + // Check if this ident is a function in the workerscript env. If it is, then we need to + // get its RAM cost. We do this by calling it, which works because the running script + // is in checkingRam mode. + // + // TODO it would be simpler to just reference a dictionary. + try { + function applyFuncRam(func) { + if (typeof func === "function") { + try { + let res; + if (func.constructor.name === "AsyncFunction") { + res = 0; // Async functions will always be 0 RAM + } else { + res = func.apply(null, []); + } + if (typeof res === "number") { + return res; + } + return 0; + } catch(e) { + console.log("ERROR applying function: " + e); + return 0; + } + } else { + return 0; + } + } + + //Special logic for namespaces (Bladeburner, CodingCOntract) + var func; + if (ref in workerScript.env.vars.bladeburner) { + func = workerScript.env.vars.bladeburner[ref]; + } else if (ref in workerScript.env.vars.codingcontract) { + func = workerScript.env.vars.codingcontract[ref]; + } else if (ref in workerScript.env.vars.gang) { + func = workerScript.env.vars.gang[ref]; + } else { + func = workerScript.env.get(ref); + } + ram += applyFuncRam(func); + } catch (error) {continue;} + } + return ram; + + } catch (error) { + // console.info("parse or eval error: ", error); + // This is not unexpected. The user may be editing a script, and it may be in + // a transitory invalid state. + return -1; + } +} + +// Parses one script and calculates its ram usage, for the global scope and each function. +// Returns a cost map and a dependencyMap for the module. Returns a reference map to be joined +// onto the main reference map, and a list of modules that need to be parsed. +function parseOnlyCalculateDeps(code, currentModule) { + const ast = parse(code, {sourceType:"module", ecmaVersion: 8}); + + // Everything from the global scope goes in ".". Everything else goes in ".function", where only + // the outermost layer of functions counts. + const globalKey = currentModule + memCheckGlobalKey; + const dependencyMap = {}; + dependencyMap[globalKey] = new Set(); + + // If we reference this internal name, we're really referencing that external name. + // Filled when we import names from other modules. + let internalToExternal = {}; + + var additionalModules = []; + + // References get added pessimistically. They are added for thisModule.name, name, and for + // any aliases. + function addRef(key, name) { + const s = dependencyMap[key] || (dependencyMap[key] = new Set()); + if (name in internalToExternal) { + s.add(internalToExternal[name]); + } + s.add(currentModule + "." + name); + s.add(name); // For builtins like hack. + } + + //A list of identifiers that resolve to "native Javascript code" + const objectPrototypeProperties = Object.getOwnPropertyNames(Object.prototype); + + // If we discover a dependency identifier, state.key is the dependent identifier. + // walkDeeper is for doing recursive walks of expressions in composites that we handle. + function commonVisitors() { + return { + Identifier: (node, st, walkDeeper) => { + if (objectPrototypeProperties.includes(node.name)) {return;} + addRef(st.key, node.name); + }, + WhileStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceWHILE); + node.test && walkDeeper(node.test, st); + node.body && walkDeeper(node.body, st); + }, + DoWhileStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceWHILE); + node.test && walkDeeper(node.test, st); + node.body && walkDeeper(node.body, st); + }, + ForStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceFOR); + node.init && walkDeeper(node.init, st); + node.test && walkDeeper(node.test, st); + node.update && walkDeeper(node.update, st); + node.body && walkDeeper(node.body, st); + }, + IfStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceIF); + node.test && walkDeeper(node.test, st); + node.consequent && walkDeeper(node.consequent, st); + node.alternate && walkDeeper(node.alternate, st); + }, + MemberExpression: (node, st, walkDeeper) => { + node.object && walkDeeper(node.object, st); + node.property && walkDeeper(node.property, st); + }, + } + } + + //Spread syntax not supported in Edge yet, use Object.assign + /* + walk.recursive(ast, {key: globalKey}, { + ImportDeclaration: (node, st, walkDeeper) => { + const importModuleName = node.source.value; + additionalModules.push(importModuleName); + + // This module's global scope refers to that module's global scope, no matter how we + // import it. + dependencyMap[st.key].add(importModuleName + memCheckGlobalKey); + + for (let i = 0; i < node.specifiers.length; ++i) { + const spec = node.specifiers[i]; + if (spec.imported !== undefined && spec.local !== undefined) { + // We depend on specific things. + internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name; + } else { + // We depend on everything. + dependencyMap[st.key].add(importModuleName + ".*"); + } + } + }, + FunctionDeclaration: (node, st, walkDeeper) => { + // Don't use walkDeeper, because we are changing the visitor set. + const key = currentModule + "." + node.id.name; + walk.recursive(node, {key: key}, commonVisitors()); + }, + ...commonVisitors() + }); + */ + walk.recursive(ast, {key: globalKey}, Object.assign({ + ImportDeclaration: (node, st, walkDeeper) => { + const importModuleName = node.source.value; + additionalModules.push(importModuleName); + + // This module's global scope refers to that module's global scope, no matter how we + // import it. + dependencyMap[st.key].add(importModuleName + memCheckGlobalKey); + + for (let i = 0; i < node.specifiers.length; ++i) { + const spec = node.specifiers[i]; + if (spec.imported !== undefined && spec.local !== undefined) { + // We depend on specific things. + internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name; + } else { + // We depend on everything. + dependencyMap[st.key].add(importModuleName + ".*"); + } + } + }, + FunctionDeclaration: (node, st, walkDeeper) => { + // Don't use walkDeeper, because we are changing the visitor set. + const key = currentModule + "." + node.id.name; + walk.recursive(node, {key: key}, commonVisitors()); + }, + }, commonVisitors())); + + return {dependencyMap: dependencyMap, additionalModules: additionalModules}; +} + +export async function calculateRamUsage(codeCopy) { + //Create a temporary/mock WorkerScript and an AST from the code + var currServ = Player.getCurrentServer(); + var workerScript = new WorkerScript({ + filename:"foo", + scriptRef: {code:""}, + args:[], + getCode: function() { return ""; } + }); + workerScript.checkingRam = true; //Netscript functions will return RAM usage + workerScript.serverIp = currServ.ip; + + try { + return await parseOnlyRamCalculate(currServ, codeCopy, workerScript); + } catch (e) { + console.log("Failed to parse ram using new method. Falling back.", e); + } + + // Try the old way. + + try { + var ast = parse(codeCopy, {sourceType:"module"}); + } catch(e) { + return -1; + } + + //Search through AST, scanning for any 'Identifier' nodes for functions, or While/For/If nodes + var queue = [], ramUsage = CONSTANTS.ScriptBaseRamCost; + var whileUsed = false, forUsed = false, ifUsed = false; + queue.push(ast); + while (queue.length != 0) { + var exp = queue.shift(); + switch (exp.type) { + case "ImportDeclaration": + //Gets an array of all imported functions as AST expressions + //and pushes them on the queue. + var res = evaluateImport(exp, workerScript, true); + for (var i = 0; i < res.length; ++i) { + queue.push(res[i]); + } + break; + case "BlockStatement": + case "Program": + for (var i = 0; i < exp.body.length; ++i) { + if (exp.body[i] instanceof Node) { + queue.push(exp.body[i]); + } + } + break; + case "WhileStatement": + if (!whileUsed) { + ramUsage += CONSTANTS.ScriptWhileRamCost; + whileUsed = true; + } + break; + case "ForStatement": + if (!forUsed) { + ramUsage += CONSTANTS.ScriptForRamCost; + forUsed = true; + } + break; + case "IfStatement": + if (!ifUsed) { + ramUsage += CONSTANTS.ScriptIfRamCost; + ifUsed = true; + } + break; + case "Identifier": + if (exp.name in workerScript.env.vars) { + var func = workerScript.env.get(exp.name); + if (typeof func === "function") { + try { + var res = func.apply(null, []); + if (typeof res === "number") { + ramUsage += res; + } + } catch(e) { + console.log("ERROR applying function: " + e); + } + } + } + break; + default: + break; + } + + for (var prop in exp) { + if (exp.hasOwnProperty(prop)) { + if (exp[prop] instanceof Node) { + queue.push(exp[prop]); + } + } + } + } + + //Special case: hacknetnodes array + if (codeCopy.includes("hacknet")) { + ramUsage += CONSTANTS.ScriptHacknetNodesRamCost; + } + return ramUsage; +} diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts index c20e3e5f5..9da52d47b 100644 --- a/src/Script/RunningScript.ts +++ b/src/Script/RunningScript.ts @@ -1,10 +1,16 @@ // Class representing a Script instance that is actively running. // A Script can have multiple active instances -import { Script } from "./Script"; -import { IMap } from "../types"; +import { Script } from "./Script"; +import { FconfSettings } from "../Fconf/FconfSettings"; +import { AllServers } from "../Server/AllServers"; +import { Settings } from "../Settings/Settings"; +import { IMap } from "../types"; +import { post } from "../ui/postToTerminal"; + import { Generic_fromJSON, Generic_toJSON, - Reviver } from "../../utils/JSONReviver"; + Reviver } from "../../utils/JSONReviver"; +import { getTimestamp } from "../../utils/helpers/getTimestamp"; export class RunningScript { // Initializes a RunningScript Object from a JSON save state @@ -67,7 +73,7 @@ export class RunningScript { this.ramUsage = script.ramUsage; } - RunningScript.prototype.getCode = function() { + getCode(): string { const server = AllServers[this.server]; if (server == null) { return ""; } for (let i = 0; i < server.scripts.length; ++i) { @@ -79,7 +85,7 @@ export class RunningScript { return ""; } - RunningScript.prototype.getRamUsage = function() { + getRamUsage(): number { if (this.ramUsage != null && this.ramUsage > 0) { return this.ramUsage; } // Use cached value const server = AllServers[this.server]; @@ -96,7 +102,7 @@ export class RunningScript { return 0; } - RunningScript.prototype.log = function(txt) { + log(txt: string): void { if (this.logs.length > Settings.MaxLogCapacity) { //Delete first element and add new log entry to the end. //TODO Eventually it might be better to replace this with circular array @@ -111,18 +117,18 @@ export class RunningScript { this.logUpd = true; } - RunningScript.prototype.displayLog = function() { + displayLog(): void { for (var i = 0; i < this.logs.length; ++i) { post(this.logs[i]); } } - RunningScript.prototype.clearLog = function() { + clearLog(): void { this.logs.length = 0; } - //Update the moneyStolen and numTimesHack maps when hacking - RunningScript.prototype.recordHack = function(serverIp, moneyGained, n=1) { + // Update the moneyStolen and numTimesHack maps when hacking + recordHack(serverIp: string, moneyGained: number, n: number=1) { if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { this.dataMap[serverIp] = [0, 0, 0, 0]; } @@ -130,16 +136,16 @@ export class RunningScript { this.dataMap[serverIp][1] += n; } - //Update the grow map when calling grow() - RunningScript.prototype.recordGrow = function(serverIp, n=1) { + // Update the grow map when calling grow() + recordGrow(serverIp: string, n: number=1) { if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { this.dataMap[serverIp] = [0, 0, 0, 0]; } this.dataMap[serverIp][2] += n; } - //Update the weaken map when calling weaken() { - RunningScript.prototype.recordWeaken = function(serverIp, n=1) { + // Update the weaken map when calling weaken() { + recordWeaken(serverIp: string, n: number=1) { if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { this.dataMap[serverIp] = [0, 0, 0, 0]; } diff --git a/src/Script/Script.ts b/src/Script/Script.ts index fa64ed0d8..92f854f40 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -1,8 +1,11 @@ // Class representing a script file // This does NOT represent a script that is actively running and // being evaluated. See RunningScript for that +import { calculateRamUsage } from "./RamCalculations"; +import { IPlayer } from "../PersonObjects/IPlayer"; import { Page, routing } from "../ui/navigationTracking"; + import { setTimeoutRef } from "../utils/SetTimeoutRef"; import { Generic_fromJSON, Generic_toJSON, @@ -61,10 +64,9 @@ export class Script { } // Save a script FROM THE SCRIPT EDITOR - saveScript(): void { + saveScript(code: string, p: IPlayer): void { if (routing.isOn(Page.ScriptEditor)) { //Update code and filename - const code = getCurrentEditor().getCode(); this.code = code.replace(/^\s+|\s+$/g, ''); const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement; @@ -75,7 +77,7 @@ export class Script { this.filename = filenameElem!.value; // Server - this.server = Player.currentServer; + this.server = p.currentServer; //Calculate/update ram usage, execution time, etc. this.updateRamUsage(); @@ -85,7 +87,7 @@ export class Script { } // Updates the script's RAM usage based on its code - async updateRamUsage(): void { + async updateRamUsage() { // TODO Commented this out because I think its unnecessary // DOuble check/Test // var codeCopy = this.code.repeat(1); diff --git a/src/Script/ScriptHelpers.js b/src/Script/ScriptHelpers.js index 45a6ebb99..821dce013 100644 --- a/src/Script/ScriptHelpers.js +++ b/src/Script/ScriptHelpers.js @@ -1,40 +1,33 @@ -// Importing this doesn't work for some reason. -const walk = require("acorn/dist/walk"); +import { calculateRamUsage } from "./RamCalculations"; +import { isScriptFilename } from "./ScriptHelpersTS"; -import {CONSTANTS} from "./Constants"; -import {Engine} from "./engine"; -import {FconfSettings, parseFconfSettings} from "./Fconf"; +import {CONSTANTS} from "../Constants"; +import {Engine} from "../engine"; +import { parseFconfSettings } from "../Fconf/Fconf"; +import { FconfSettings } from "../Fconf/FconfSettings"; import {iTutorialSteps, iTutorialNextStep, - ITutorial} from "./InteractiveTutorial"; -import {evaluateImport} from "./NetscriptEvaluator"; -import {NetscriptFunctions} from "./NetscriptFunctions"; -import {addWorkerScript, WorkerScript} from "./NetscriptWorker"; -import {Player} from "./Player"; -import { AceEditor } from "./ScriptEditor/Ace"; -import { CodeMirrorEditor } from "./ScriptEditor/CodeMirror"; -import {AllServers, processSingleServerGrowth} from "./Server"; -import { Settings } from "./Settings/Settings"; -import { EditorSetting } from "./Settings/SettingEnums"; -import {post} from "./ui/postToTerminal"; -import {TextFile} from "./TextFile"; -import {parse, Node} from "../utils/acorn"; -import {Page, routing} from "./ui/navigationTracking"; -import {numeralWrapper} from "./ui/numeralFormat"; -import { setTimeoutRef } from "./utils/SetTimeoutRef"; -import {dialogBoxCreate} from "../utils/DialogBox"; -import {Reviver, Generic_toJSON, - Generic_fromJSON} from "../utils/JSONReviver"; -import {compareArrays} from "../utils/helpers/compareArrays"; -import {createElement} from "../utils/uiHelpers/createElement"; -import {getTimestamp} from "../utils/helpers/getTimestamp"; -import {roundToTwo} from "../utils/helpers/roundToTwo"; + ITutorial} from "../InteractiveTutorial"; +import { addWorkerScript } from "../NetscriptWorker"; +import { Player } from "../Player"; +import { AceEditor } from "../ScriptEditor/Ace"; +import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror"; +import { AllServers } from "../Server/AllServers"; +import { processSingleServerGrowth } from "../Server/ServerHelpers"; +import { Settings } from "../Settings/Settings"; +import { EditorSetting } from "../Settings/SettingEnums"; +import {TextFile} from "../TextFile"; -function isScriptFilename(f) { - return f.endsWith(".js") || f.endsWith(".script") || f.endsWith(".ns"); -} +import {Page, routing} from "../ui/navigationTracking"; +import {numeralWrapper} from "../ui/numeralFormat"; + +import {dialogBoxCreate} from "../../utils/DialogBox"; +import {Reviver, Generic_toJSON, + Generic_fromJSON} from "../../utils/JSONReviver"; +import {compareArrays} from "../../utils/helpers/compareArrays"; +import {createElement} from "../../utils/uiHelpers/createElement"; var scriptEditorRamCheck = null, scriptEditorRamText = null; -function scriptEditorInit() { +export function scriptEditorInit() { // Wrapper container that holds all the buttons below the script editor const wrapper = document.getElementById("script-editor-buttons-wrapper"); if (wrapper == null) { @@ -233,7 +226,7 @@ function saveAndCloseScriptEditor() { let s = Player.getCurrentServer(); for (var i = 0; i < s.scripts.length; i++) { if (filename == s.scripts[i].filename) { - s.scripts[i].saveScript(); + s.scripts[i].saveScript(getCurrentEditor().getCode(), Player); Engine.loadTerminalContent(); return iTutorialNextStep(); } @@ -241,7 +234,7 @@ function saveAndCloseScriptEditor() { //If the current script does NOT exist, create a new one let script = new Script(); - script.saveScript(); + script.saveScript(getCurrentEditor().getCode(), Player); s.scripts.push(script); return iTutorialNextStep(); @@ -269,7 +262,7 @@ function saveAndCloseScriptEditor() { //If the current script already exists on the server, overwrite it for (var i = 0; i < s.scripts.length; i++) { if (filename == s.scripts[i].filename) { - s.scripts[i].saveScript(); + s.scripts[i].saveScript(getCurrentEditor().getCode(), Player); Engine.loadTerminalContent(); return; } @@ -277,7 +270,7 @@ function saveAndCloseScriptEditor() { //If the current script does NOT exist, create a new one var script = new Script(); - script.saveScript(); + script.saveScript(getCurrentEditor().getCode(), Player); s.scripts.push(script); } else if (filename.endsWith(".txt")) { for (var i = 0; i < s.textFiles.length; ++i) { @@ -308,410 +301,9 @@ function checkValidFilename(filename) { return false; } -// These special strings are used to reference the presence of a given logical -// construct within a user script. -const specialReferenceIF = "__SPECIAL_referenceIf"; -const specialReferenceFOR = "__SPECIAL_referenceFor"; -const specialReferenceWHILE = "__SPECIAL_referenceWhile"; - -// The global scope of a script is registered under this key during parsing. -const memCheckGlobalKey = ".__GLOBAL__"; - -// Calcluates the amount of RAM a script uses. Uses parsing and AST walking only, -// rather than NetscriptEvaluator. This is useful because NetscriptJS code does -// not work under NetscriptEvaluator. -async function parseOnlyRamCalculate(server, code, workerScript) { - try { - // Maps dependent identifiers to their dependencies. - // - // The initial identifier is __SPECIAL_INITIAL_MODULE__.__GLOBAL__. - // It depends on all the functions declared in the module, all the global scopes - // of its imports, and any identifiers referenced in this global scope. Each - // function depends on all the identifiers referenced internally. - // We walk the dependency graph to calculate RAM usage, given that some identifiers - // reference Netscript functions which have a RAM cost. - let dependencyMap = {}; - - // Scripts we've parsed. - const completedParses = new Set(); - - // Scripts we've discovered that need to be parsed. - const parseQueue = []; - - // Parses a chunk of code with a given module name, and updates parseQueue and dependencyMap. - function parseCode(code, moduleName) { - const result = parseOnlyCalculateDeps(code, moduleName); - completedParses.add(moduleName); - - // Add any additional modules to the parse queue; - for (let i = 0; i < result.additionalModules.length; ++i) { - if (!completedParses.has(result.additionalModules[i])) { - parseQueue.push(result.additionalModules[i]); - } - } - - // Splice all the references in. - //Spread syntax not supported in edge, use Object.assign instead - //dependencyMap = {...dependencyMap, ...result.dependencyMap}; - dependencyMap = Object.assign(dependencyMap, result.dependencyMap); - } - - const initialModule = "__SPECIAL_INITIAL_MODULE__"; - parseCode(code, initialModule); - - while (parseQueue.length > 0) { - // Get the code from the server. - const nextModule = parseQueue.shift(); - - let code; - if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) { - try { - const module = await eval('import(nextModule)'); - code = ""; - for (const prop in module) { - if (typeof module[prop] === 'function') { - code += module[prop].toString() + ";\n"; - } - } - } catch(e) { - console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`); - return -1; - } - } else { - const script = server.getScript(nextModule.startsWith("./") ? nextModule.slice(2) : nextModule); - if (!script) { - console.warn("Invalid script"); - return -1; // No such script on the server. - } - code = script.code; - } - - parseCode(code, nextModule); - } - - // Finally, walk the reference map and generate a ram cost. The initial set of keys to scan - // are those that start with __SPECIAL_INITIAL_MODULE__. - let ram = CONSTANTS.ScriptBaseRamCost; - const unresolvedRefs = Object.keys(dependencyMap).filter(s => s.startsWith(initialModule)); - const resolvedRefs = new Set(); - while (unresolvedRefs.length > 0) { - const ref = unresolvedRefs.shift(); - - // Check if this is one of the special keys, and add the appropriate ram cost if so. - if (ref === "hacknet" && !resolvedRefs.has("hacknet")) { - ram += CONSTANTS.ScriptHacknetNodesRamCost; - } - if (ref === "document" && !resolvedRefs.has("document")) { - ram += CONSTANTS.ScriptDomRamCost; - } - if (ref === "window" && !resolvedRefs.has("window")) { - ram += CONSTANTS.ScriptDomRamCost; - } - - resolvedRefs.add(ref); - - if (ref.endsWith(".*")) { - // A prefix reference. We need to find all matching identifiers. - const prefix = ref.slice(0, ref.length - 2); - for (let ident of Object.keys(dependencyMap).filter(k => k.startsWith(prefix))) { - for (let dep of dependencyMap[ident] || []) { - if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); - } - } - } else { - // An exact reference. Add all dependencies of this ref. - for (let dep of dependencyMap[ref] || []) { - if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); - } - } - - // Check if this ident is a function in the workerscript env. If it is, then we need to - // get its RAM cost. We do this by calling it, which works because the running script - // is in checkingRam mode. - // - // TODO it would be simpler to just reference a dictionary. - try { - function applyFuncRam(func) { - if (typeof func === "function") { - try { - let res; - if (func.constructor.name === "AsyncFunction") { - res = 0; // Async functions will always be 0 RAM - } else { - res = func.apply(null, []); - } - if (typeof res === "number") { - return res; - } - return 0; - } catch(e) { - console.log("ERROR applying function: " + e); - return 0; - } - } else { - return 0; - } - } - - //Special logic for namespaces (Bladeburner, CodingCOntract) - var func; - if (ref in workerScript.env.vars.bladeburner) { - func = workerScript.env.vars.bladeburner[ref]; - } else if (ref in workerScript.env.vars.codingcontract) { - func = workerScript.env.vars.codingcontract[ref]; - } else if (ref in workerScript.env.vars.gang) { - func = workerScript.env.vars.gang[ref]; - } else { - func = workerScript.env.get(ref); - } - ram += applyFuncRam(func); - } catch (error) {continue;} - } - return ram; - - } catch (error) { - // console.info("parse or eval error: ", error); - // This is not unexpected. The user may be editing a script, and it may be in - // a transitory invalid state. - return -1; - } -} - -// Parses one script and calculates its ram usage, for the global scope and each function. -// Returns a cost map and a dependencyMap for the module. Returns a reference map to be joined -// onto the main reference map, and a list of modules that need to be parsed. -function parseOnlyCalculateDeps(code, currentModule) { - const ast = parse(code, {sourceType:"module", ecmaVersion: 8}); - - // Everything from the global scope goes in ".". Everything else goes in ".function", where only - // the outermost layer of functions counts. - const globalKey = currentModule + memCheckGlobalKey; - const dependencyMap = {}; - dependencyMap[globalKey] = new Set(); - - // If we reference this internal name, we're really referencing that external name. - // Filled when we import names from other modules. - let internalToExternal = {}; - - var additionalModules = []; - - // References get added pessimistically. They are added for thisModule.name, name, and for - // any aliases. - function addRef(key, name) { - const s = dependencyMap[key] || (dependencyMap[key] = new Set()); - if (name in internalToExternal) { - s.add(internalToExternal[name]); - } - s.add(currentModule + "." + name); - s.add(name); // For builtins like hack. - } - - //A list of identifiers that resolve to "native Javascript code" - const objectPrototypeProperties = Object.getOwnPropertyNames(Object.prototype); - - // If we discover a dependency identifier, state.key is the dependent identifier. - // walkDeeper is for doing recursive walks of expressions in composites that we handle. - function commonVisitors() { - return { - Identifier: (node, st, walkDeeper) => { - if (objectPrototypeProperties.includes(node.name)) {return;} - addRef(st.key, node.name); - }, - WhileStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceWHILE); - node.test && walkDeeper(node.test, st); - node.body && walkDeeper(node.body, st); - }, - DoWhileStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceWHILE); - node.test && walkDeeper(node.test, st); - node.body && walkDeeper(node.body, st); - }, - ForStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceFOR); - node.init && walkDeeper(node.init, st); - node.test && walkDeeper(node.test, st); - node.update && walkDeeper(node.update, st); - node.body && walkDeeper(node.body, st); - }, - IfStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceIF); - node.test && walkDeeper(node.test, st); - node.consequent && walkDeeper(node.consequent, st); - node.alternate && walkDeeper(node.alternate, st); - }, - MemberExpression: (node, st, walkDeeper) => { - node.object && walkDeeper(node.object, st); - node.property && walkDeeper(node.property, st); - }, - } - } - - //Spread syntax not supported in Edge yet, use Object.assign - /* - walk.recursive(ast, {key: globalKey}, { - ImportDeclaration: (node, st, walkDeeper) => { - const importModuleName = node.source.value; - additionalModules.push(importModuleName); - - // This module's global scope refers to that module's global scope, no matter how we - // import it. - dependencyMap[st.key].add(importModuleName + memCheckGlobalKey); - - for (let i = 0; i < node.specifiers.length; ++i) { - const spec = node.specifiers[i]; - if (spec.imported !== undefined && spec.local !== undefined) { - // We depend on specific things. - internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name; - } else { - // We depend on everything. - dependencyMap[st.key].add(importModuleName + ".*"); - } - } - }, - FunctionDeclaration: (node, st, walkDeeper) => { - // Don't use walkDeeper, because we are changing the visitor set. - const key = currentModule + "." + node.id.name; - walk.recursive(node, {key: key}, commonVisitors()); - }, - ...commonVisitors() - }); - */ - walk.recursive(ast, {key: globalKey}, Object.assign({ - ImportDeclaration: (node, st, walkDeeper) => { - const importModuleName = node.source.value; - additionalModules.push(importModuleName); - - // This module's global scope refers to that module's global scope, no matter how we - // import it. - dependencyMap[st.key].add(importModuleName + memCheckGlobalKey); - - for (let i = 0; i < node.specifiers.length; ++i) { - const spec = node.specifiers[i]; - if (spec.imported !== undefined && spec.local !== undefined) { - // We depend on specific things. - internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name; - } else { - // We depend on everything. - dependencyMap[st.key].add(importModuleName + ".*"); - } - } - }, - FunctionDeclaration: (node, st, walkDeeper) => { - // Don't use walkDeeper, because we are changing the visitor set. - const key = currentModule + "." + node.id.name; - walk.recursive(node, {key: key}, commonVisitors()); - }, - }, commonVisitors())); - - return {dependencyMap: dependencyMap, additionalModules: additionalModules}; -} - -async function calculateRamUsage(codeCopy) { - //Create a temporary/mock WorkerScript and an AST from the code - var currServ = Player.getCurrentServer(); - var workerScript = new WorkerScript({ - filename:"foo", - scriptRef: {code:""}, - args:[], - getCode: function() { return ""; } - }); - workerScript.checkingRam = true; //Netscript functions will return RAM usage - workerScript.serverIp = currServ.ip; - - try { - return await parseOnlyRamCalculate(currServ, codeCopy, workerScript); - } catch (e) { - console.log("Failed to parse ram using new method. Falling back.", e); - } - - // Try the old way. - - try { - var ast = parse(codeCopy, {sourceType:"module"}); - } catch(e) { - return -1; - } - - //Search through AST, scanning for any 'Identifier' nodes for functions, or While/For/If nodes - var queue = [], ramUsage = CONSTANTS.ScriptBaseRamCost; - var whileUsed = false, forUsed = false, ifUsed = false; - queue.push(ast); - while (queue.length != 0) { - var exp = queue.shift(); - switch (exp.type) { - case "ImportDeclaration": - //Gets an array of all imported functions as AST expressions - //and pushes them on the queue. - var res = evaluateImport(exp, workerScript, true); - for (var i = 0; i < res.length; ++i) { - queue.push(res[i]); - } - break; - case "BlockStatement": - case "Program": - for (var i = 0; i < exp.body.length; ++i) { - if (exp.body[i] instanceof Node) { - queue.push(exp.body[i]); - } - } - break; - case "WhileStatement": - if (!whileUsed) { - ramUsage += CONSTANTS.ScriptWhileRamCost; - whileUsed = true; - } - break; - case "ForStatement": - if (!forUsed) { - ramUsage += CONSTANTS.ScriptForRamCost; - forUsed = true; - } - break; - case "IfStatement": - if (!ifUsed) { - ramUsage += CONSTANTS.ScriptIfRamCost; - ifUsed = true; - } - break; - case "Identifier": - if (exp.name in workerScript.env.vars) { - var func = workerScript.env.get(exp.name); - if (typeof func === "function") { - try { - var res = func.apply(null, []); - if (typeof res === "number") { - ramUsage += res; - } - } catch(e) { - console.log("ERROR applying function: " + e); - } - } - } - break; - default: - break; - } - - for (var prop in exp) { - if (exp.hasOwnProperty(prop)) { - if (exp[prop] instanceof Node) { - queue.push(exp[prop]); - } - } - } - } - - //Special case: hacknetnodes array - if (codeCopy.includes("hacknet")) { - ramUsage += CONSTANTS.ScriptHacknetNodesRamCost; - } - return ramUsage; -} - //Called when the game is loaded. Loads all running scripts (from all servers) //into worker scripts so that they will start running -function loadAllRunningScripts() { +export function loadAllRunningScripts() { var total = 0; let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1); if (skipScriptLoad) { console.info("Skipping the load of any scripts during startup"); } @@ -767,7 +359,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) { var timesGrown = Math.round(0.5 * runningScriptObj.dataMap[ip][2] / runningScriptObj.onlineRunningTime * timePassed); console.log(runningScriptObj.filename + " called grow() on " + serv.hostname + " " + timesGrown + " times while offline"); runningScriptObj.log("Called grow() on " + serv.hostname + " " + timesGrown + " times while offline"); - var growth = processSingleServerGrowth(serv, timesGrown * 450); + var growth = processSingleServerGrowth(serv, timesGrown * 450, Player); runningScriptObj.log(serv.hostname + " grown by " + numeralWrapper.format(growth * 100 - 100, '0.000000%') + " from grow() calls made while offline"); } } @@ -838,7 +430,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) { //Returns a RunningScript object matching the filename and arguments on the //designated server, and false otherwise -function findRunningScript(filename, args, server) { +export function findRunningScript(filename, args, server) { for (var i = 0; i < server.runningScripts.length; ++i) { if (server.runningScripts[i].filename == filename && compareArrays(server.runningScripts[i].args, args)) { @@ -847,6 +439,3 @@ function findRunningScript(filename, args, server) { } return null; } - -export {loadAllRunningScripts, findRunningScript, - scriptEditorInit, isScriptFilename}; diff --git a/src/Script/ScriptHelpersTS.ts b/src/Script/ScriptHelpersTS.ts new file mode 100644 index 000000000..f59b69810 --- /dev/null +++ b/src/Script/ScriptHelpersTS.ts @@ -0,0 +1,4 @@ +// Script helper functions +export function isScriptFilename(f: string) { + return f.endsWith(".js") || f.endsWith(".script") || f.endsWith(".ns"); +} diff --git a/src/Server/AllServers.ts b/src/Server/AllServers.ts index e04c33fbb..6d8d91c0b 100644 --- a/src/Server/AllServers.ts +++ b/src/Server/AllServers.ts @@ -1,40 +1,61 @@ -import { ipExists } from "../../utils/IPAddress"; +import { Server } from "./Server"; +import { SpecialServerIps } from "./SpecialServerIps"; +import { serverMetadata } from "./data/servers"; + +import { IMap } from "../types"; +import { createRandomIp, + ipExists } from "../../utils/IPAddress"; +import { getRandomInt } from "../../utils/helpers/getRandomInt"; +import { Reviver } from "../../utils/JSONReviver"; // Map of all Servers that exist in the game // Key (string) = IP // Value = Server object -let AllServers = {}; +export let AllServers: IMap = {}; // Saftely add a Server to the AllServers map -export function AddToAllServers(server) { +export function AddToAllServers(server: Server): void { var serverIp = server.ip; if (ipExists(serverIp)) { console.log("IP of server that's being added: " + serverIp); console.log("Hostname of the server thats being added: " + server.hostname); console.log("The server that already has this IP is: " + AllServers[serverIp].hostname); throw new Error("Error: Trying to add a server with an existing IP"); - return; } AllServers[serverIp] = server; } -export function initForeignServers() { +interface IServerParams { + hackDifficulty?: number; + hostname: string; + ip: string; + maxRam?: number; + moneyAvailable?: number; + numOpenPortsRequired: number; + organizationName: string; + requiredHackingSkill?: number; + serverGrowth?: number; + + [key: string]: any; +} + +export function initForeignServers(homeComputer: Server) { /* Create a randomized network for all the foreign servers */ //Groupings for creating a randomized network - const networkLayers = []; + const networkLayers: Server[][] = []; for (let i = 0; i < 15; i++) { networkLayers.push([]); } // Essentially any property that is of type 'number | IMinMaxRange' - const propertiesToPatternMatch = [ + const propertiesToPatternMatch: string[] = [ "hackDifficulty", "moneyAvailable", "requiredHackingSkill", "serverGrowth" ]; - const toNumber = (value) => { + const toNumber = (value: any) => { switch (typeof value) { case 'number': return value; @@ -46,7 +67,7 @@ export function initForeignServers() { } for (const metadata of serverMetadata) { - const serverParams = { + const serverParams: IServerParams = { hostname: metadata.hostname, ip: createRandomIp(), numOpenPortsRequired: metadata.numOpenPortsRequired, @@ -79,21 +100,21 @@ export function initForeignServers() { } /* Create a randomized network for all the foreign servers */ - const linkComputers = (server1, server2) => { + const linkComputers = (server1: Server, server2: Server) => { server1.serversOnNetwork.push(server2.ip); server2.serversOnNetwork.push(server1.ip); }; - const getRandomArrayItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; + const getRandomArrayItem = (arr: any[]) => arr[Math.floor(Math.random() * arr.length)]; - const linkNetworkLayers = (network1, selectServer) => { + const linkNetworkLayers = (network1: Server[], selectServer: () => Server) => { for (const server of network1) { linkComputers(server, selectServer()); } }; // Connect the first tier of servers to the player's home computer - linkNetworkLayers(networkLayers[0], () => Player.getHomeComputer()); + linkNetworkLayers(networkLayers[0], () => homeComputer); for (let i = 1; i < networkLayers.length; i++) { linkNetworkLayers(networkLayers[i], () => getRandomArrayItem(networkLayers[i - 1])); } @@ -106,6 +127,6 @@ export function prestigeAllServers() { AllServers = {}; } -export function loadAllServers(saveString) { +export function loadAllServers(saveString: string) { AllServers = JSON.parse(saveString, Reviver); } diff --git a/src/Server/Server.ts b/src/Server/Server.ts index b4d1f33dc..bc3056d14 100644 --- a/src/Server/Server.ts +++ b/src/Server/Server.ts @@ -1,9 +1,14 @@ // Class representing a single generic Server + +// TODO This import is a circular import. Try to fix it in the future +import { GetServerByHostname } from "./ServerHelpers"; + import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { CodingContract } from "../CodingContracts"; import { Message } from "../Message/Message"; import { RunningScript } from "../Script/RunningScript"; import { Script } from "../Script/Script"; +import { isScriptFilename } from "../Script/ScriptHelpersTS"; import { TextFile } from "../TextFile"; import { createRandomIp } from "../../utils/IPAddress"; @@ -27,6 +32,11 @@ interface IConstructorParams { } export class Server { + // Initializes a Server Object from a JSON save state + static fromJSON(value: any): Server { + return Generic_fromJSON(Server, value.data); + } + // Initial server security level // (i.e. security level when the server was created) baseDifficulty: number = 1; @@ -172,49 +182,43 @@ export class Server { this.maxRam = ram; } - //The serverOnNetwork array holds the IP of all the servers. This function - //returns the actual Server objects - Server.prototype.getServerOnNetwork = function(i) { - if (i > this.serversOnNetwork.length) { - console.log("Tried to get server on network that was out of range"); - return; - } - return AllServers[this.serversOnNetwork[i]]; - } - - //Given the name of the script, returns the corresponding - //script object on the server (if it exists) - Server.prototype.getScript = function(scriptName) { - for (var i = 0; i < this.scripts.length; i++) { - if (this.scripts[i].filename == scriptName) { + // Given the name of the script, returns the corresponding + // script object on the server (if it exists) + getScript(scriptName: string): Script | null { + for (let i = 0; i < this.scripts.length; i++) { + if (this.scripts[i].filename === scriptName) { return this.scripts[i]; } } + return null; } - Server.prototype.capDifficulty = function() { + // Ensures that the server's difficulty (server security) doesn't get too high + capDifficulty(): void { if (this.hackDifficulty < this.minDifficulty) {this.hackDifficulty = this.minDifficulty;} if (this.hackDifficulty < 1) {this.hackDifficulty = 1;} - //Place some arbitrarily limit that realistically should never happen unless someone is - //screwing around with the game + + // Place some arbitrarily limit that realistically should never happen unless someone is + // screwing around with the game if (this.hackDifficulty > 1000000) {this.hackDifficulty = 1000000;} } - //Strengthens a server's security level (difficulty) by the specified amount - Server.prototype.fortify = function(amt) { + // Strengthens a server's security level (difficulty) by the specified amount + fortify(amt: number): void { this.hackDifficulty += amt; this.capDifficulty(); } - Server.prototype.weaken = function(amt) { + // Lowers the server's security level (difficulty) by the specified amount) + weaken(amt: number): void { this.hackDifficulty -= (amt * BitNodeMultipliers.ServerWeakenRate); this.capDifficulty(); } // Write to a script file // Overwrites existing files. Creates new files if the script does not eixst - Server.prototype.writeToScriptFile = function(fn, code) { + writeToScriptFile(fn: string, code: string) { var ret = {success: false, overwritten: false}; if (!isScriptFilename(fn)) { return ret; } @@ -232,7 +236,7 @@ export class Server { } //Otherwise, create a new script - var newScript = new Script(); + const newScript = new Script(); newScript.filename = fn; newScript.code = code; newScript.updateRamUsage(); @@ -244,8 +248,8 @@ export class Server { // Write to a text file // Overwrites existing files. Creates new files if the text file does not exist - Server.prototype.writeToTextFile = function(fn, txt) { - var ret = {success: false, overwritten: false}; + writeToTextFile(fn: string, txt: string) { + var ret = { success: false, overwritten: false }; if (!fn.endsWith("txt")) { return ret; } //Check if the text file already exists, and overwrite if it does @@ -265,11 +269,11 @@ export class Server { return ret; } - Server.prototype.addContract = function(contract) { + addContract(contract: CodingContract) { this.contracts.push(contract); } - Server.prototype.removeContract = function(contract) { + removeContract(contract: CodingContract) { if (contract instanceof CodingContract) { this.contracts = this.contracts.filter((c) => { return c.fn !== contract.fn; @@ -281,7 +285,7 @@ export class Server { } } - Server.prototype.getContract = function(contractName) { + getContract(contractName: string) { for (const contract of this.contracts) { if (contract.fn === contractName) { return contract; @@ -289,15 +293,11 @@ export class Server { } return null; } -} -//Functions for loading and saving a Server -Server.prototype.toJSON = function() { - return Generic_toJSON("Server", this); -} - -Server.fromJSON = function(value) { - return Generic_fromJSON(Server, value.data); + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("Server", this); + } } Reviver.constructors.Server = Server; diff --git a/src/Server/ServerHelpers.js b/src/Server/ServerHelpers.ts similarity index 67% rename from src/Server/ServerHelpers.js rename to src/Server/ServerHelpers.ts index 0ab1183f5..e443deb0c 100644 --- a/src/Server/ServerHelpers.js +++ b/src/Server/ServerHelpers.ts @@ -1,23 +1,16 @@ -import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; -import { CodingContract, - ContractTypes } from "./CodingContracts"; -import { CONSTANTS } from "./Constants"; -import { Script, - isScriptFilename } from "./Script"; -import { Player } from "./Player"; -import { Programs } from "./Programs/Programs"; -import { SpecialServerIps } from "./SpecialServerIps"; -import { TextFile } from "./TextFile"; -import { getRandomInt } from "../utils/helpers/getRandomInt"; -import { serverMetadata } from "./data/servers"; -import { Reviver, - Generic_toJSON, - Generic_fromJSON} from "../utils/JSONReviver"; -import {isValidIPAddress} from "../utils/helpers/isValidIPAddress"; +import { AllServers } from "./AllServers"; +import { Server } from "./Server"; + +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import { CONSTANTS } from "../Constants"; +import { IPlayer } from "../PersonObjects/IPlayer"; +import { Programs } from "../Programs/Programs"; + +import {isValidIPAddress} from "../../utils/helpers/isValidIPAddress"; // Returns the number of cycles needed to grow the specified server by the // specified amount. 'growth' parameter is in decimal form, not percentage -export function numCycleForGrowth(server, growth) { +export function numCycleForGrowth(server: Server, growth: number, p: IPlayer) { let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty; if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) { ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate; @@ -25,12 +18,12 @@ export function numCycleForGrowth(server, growth) { const serverGrowthPercentage = server.serverGrowth / 100; - const cycles = Math.log(growth)/(Math.log(ajdGrowthRate)*Player.hacking_grow_mult*serverGrowthPercentage); + const cycles = Math.log(growth)/(Math.log(ajdGrowthRate) * p.hacking_grow_mult * serverGrowthPercentage); return cycles; } //Applied server growth for a single server. Returns the percentage growth -export function processSingleServerGrowth(server, numCycles) { +export function processSingleServerGrowth(server: Server, numCycles: number, p: IPlayer) { //Server growth processed once every 450 game cycles const numServerGrowthCycles = Math.max(Math.floor(numCycles / 450), 0); @@ -44,7 +37,7 @@ export function processSingleServerGrowth(server, numCycles) { const numServerGrowthCyclesAdjusted = numServerGrowthCycles * serverGrowthPercentage * BitNodeMultipliers.ServerGrowthRate; //Apply serverGrowth for the calculated number of growth cycles - var serverGrowth = Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * Player.hacking_grow_mult); + let serverGrowth = Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * p.hacking_grow_mult); if (serverGrowth < 1) { console.log("WARN: serverGrowth calculated to be less than 1"); serverGrowth = 1; @@ -66,14 +59,14 @@ export function processSingleServerGrowth(server, numCycles) { // if there was any growth at all, increase security if (oldMoneyAvailable !== server.moneyAvailable) { //Growing increases server security twice as much as hacking - let usedCycles = numCycleForGrowth(server, server.moneyAvailable / oldMoneyAvailable); + let usedCycles = numCycleForGrowth(server, server.moneyAvailable / oldMoneyAvailable, p); usedCycles = Math.max(0, usedCycles); server.fortify(2 * CONSTANTS.ServerFortifyAmount * Math.ceil(usedCycles)); } return server.moneyAvailable / oldMoneyAvailable; } -export function prestigeHomeComputer(homeComp) { +export function prestigeHomeComputer(homeComp: Server) { const hasBitflume = homeComp.programs.includes(Programs.BitFlume.name); homeComp.programs.length = 0; //Remove programs @@ -93,17 +86,9 @@ export function prestigeHomeComputer(homeComp) { homeComp.messages.push("hackers-starting-handbook.lit"); } -function SizeOfAllServers() { - var size = 0, key; - for (key in AllServers) { - if (AllServers.hasOwnProperty(key)) size++; - } - return size; -} - //Returns server object with corresponding hostname // Relatively slow, would rather not use this a lot -export function GetServerByHostname(hostname) { +export function GetServerByHostname(hostname: string): Server | null { for (var ip in AllServers) { if (AllServers.hasOwnProperty(ip)) { if (AllServers[ip].hostname == hostname) { @@ -111,16 +96,30 @@ export function GetServerByHostname(hostname) { } } } + return null; } //Get server by IP or hostname. Returns null if invalid -export function getServer(s) { +export function getServer(s: string): Server | null { if (!isValidIPAddress(s)) { return GetServerByHostname(s); } - if(AllServers[s] !== undefined) { + if (AllServers[s] !== undefined) { return AllServers[s]; } + return null; } + +// Returns the i-th server on the specified server's network +// A Server's serverOnNetwork property holds only the IPs. This function returns +// the actual Server object +export function getServerOnNetwork(server: Server, i: number) { + if (i > server.serversOnNetwork.length) { + console.error("Tried to get server on network that was out of range"); + return; + } + + return AllServers[server.serversOnNetwork[i]]; +} diff --git a/src/Server/ServerPurchases.js b/src/Server/ServerPurchases.js index 00535b997..163a725d1 100644 --- a/src/Server/ServerPurchases.js +++ b/src/Server/ServerPurchases.js @@ -2,16 +2,16 @@ * Implements functions for purchasing servers or purchasing more RAM for * the home computer */ -import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; -import { CONSTANTS } from "./Constants"; -import { Player } from "./Player"; -import { Server, - AllServers, - AddToAllServers} from "./Server"; -import { dialogBoxCreate } from "../utils/DialogBox"; -import { createRandomIp } from "../utils/IPAddress"; -import { yesNoTxtInpBoxGetInput } from "../utils/YesNoBox"; -import { isPowerOfTwo } from "../utils/helpers/isPowerOfTwo"; +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import { CONSTANTS } from "../Constants"; +import { Player } from "../Player"; +import { AllServers } from "../Server/AllServers"; +import { Server } from "../Server/Server"; +import { AddToAllServers } from "../Server/ServerHelpers"; +import { dialogBoxCreate } from "../../utils/DialogBox"; +import { createRandomIp } from "../../utils/IPAddress"; +import { yesNoTxtInpBoxGetInput } from "../../utils/YesNoBox"; +import { isPowerOfTwo } from "../../utils/helpers/isPowerOfTwo"; // Returns the cost of purchasing a server with the given RAM // Returns Infinity for invalid 'ram' arguments diff --git a/src/Server/SpecialServerIps.js b/src/Server/SpecialServerIps.js deleted file mode 100644 index d3175dc01..000000000 --- a/src/Server/SpecialServerIps.js +++ /dev/null @@ -1,50 +0,0 @@ -import {Reviver, Generic_toJSON, - Generic_fromJSON} from "../utils/JSONReviver"; - -/* Holds IP of Special Servers */ -let SpecialServerNames = { - FulcrumSecretTechnologies: "Fulcrum Secret Technologies Server", - CyberSecServer: "CyberSec Server", - NiteSecServer: "NiteSec Server", - TheBlackHandServer: "The Black Hand Server", - BitRunnersServer: "BitRunners Server", - TheDarkArmyServer: "The Dark Army Server", - DaedalusServer: "Daedalus Server", - WorldDaemon: "w0r1d_d43m0n", -} -function SpecialServerIpsMap() {} - -SpecialServerIpsMap.prototype.addIp = function(name, ip) { - this[name] = ip; -} - -SpecialServerIpsMap.prototype.toJSON = function() { - return Generic_toJSON("SpecialServerIpsMap", this); -} - -SpecialServerIpsMap.fromJSON = function(value) { - return Generic_fromJSON(SpecialServerIpsMap, value.data); -} - -Reviver.constructors.SpecialServerIpsMap = SpecialServerIpsMap; - -let SpecialServerIps = new SpecialServerIpsMap(); - -function prestigeSpecialServerIps() { - for (var member in SpecialServerIps) { - delete SpecialServerIps[member]; - } - SpecialServerIps = null; - SpecialServerIps = new SpecialServerIpsMap(); -} - -function loadSpecialServerIps(saveString) { - SpecialServerIps = JSON.parse(saveString, Reviver); -} - -function initSpecialServerIps() { - SpecialServerIps = new SpecialServerIpsMap(); -} - -export {SpecialServerNames, SpecialServerIps, SpecialServerIpsMap, loadSpecialServerIps, - prestigeSpecialServerIps, initSpecialServerIps}; diff --git a/src/Server/SpecialServerIps.ts b/src/Server/SpecialServerIps.ts new file mode 100644 index 000000000..121c377cc --- /dev/null +++ b/src/Server/SpecialServerIps.ts @@ -0,0 +1,56 @@ +import { IMap } from "../types"; +import { Reviver, + Generic_toJSON, + Generic_fromJSON } from "../../utils/JSONReviver"; + +/* Holds IP of Special Servers */ +export let SpecialServerNames: IMap = { + FulcrumSecretTechnologies: "Fulcrum Secret Technologies Server", + CyberSecServer: "CyberSec Server", + NiteSecServer: "NiteSec Server", + TheBlackHandServer: "The Black Hand Server", + BitRunnersServer: "BitRunners Server", + TheDarkArmyServer: "The Dark Army Server", + DaedalusServer: "Daedalus Server", + WorldDaemon: "w0r1d_d43m0n", +} + +export class SpecialServerIpsMap { + // Initializes a SpecialServerIpsMap Object from a JSON save state + static fromJSON(value: any): SpecialServerIpsMap { + return Generic_fromJSON(SpecialServerIpsMap, value.data); + } + + [key: string]: Function | string; + + constructor() {} + + addIp(name:string, ip: string) { + this[name] = ip; + } + + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("SpecialServerIpsMap", this); + } +} + +Reviver.constructors.SpecialServerIpsMap = SpecialServerIpsMap; + +export let SpecialServerIps: SpecialServerIpsMap = new SpecialServerIpsMap(); + +export function prestigeSpecialServerIps() { + for (var member in SpecialServerIps) { + delete SpecialServerIps[member]; + } + + SpecialServerIps = new SpecialServerIpsMap(); +} + +export function loadSpecialServerIps(saveString: string) { + SpecialServerIps = JSON.parse(saveString, Reviver); +} + +export function initSpecialServerIps() { + SpecialServerIps = new SpecialServerIpsMap(); +} diff --git a/src/data/servers.ts b/src/Server/data/servers.ts similarity index 99% rename from src/data/servers.ts rename to src/Server/data/servers.ts index 12698a3b8..ec661c458 100644 --- a/src/data/servers.ts +++ b/src/Server/data/servers.ts @@ -81,6 +81,8 @@ interface IServerMetadata { * A "unique" server that has special implications when the player manually hacks it. */ specialName?: string; + + [key: string]: any; } /** diff --git a/src/Terminal.js b/src/Terminal.js index bb06b6c25..d2744b2c9 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -10,8 +10,9 @@ import { executeDarkwebTerminalCommand, checkIfConnectedToDarkweb } from "./DarkWeb/DarkWeb"; import { DarkWebItems } from "./DarkWeb/DarkWebItems"; import {Engine} from "./engine"; -import {FconfSettings, parseFconfSettings, - createFconf} from "./Fconf"; +import { parseFconfSettings, + createFconf } from "./Fconf/Fconf"; +import { FconfSettings } from "./Fconf/FconfSettings"; import {calculateHackingChance, calculateHackingExpGain, calculatePercentMoneyHacked, @@ -22,18 +23,22 @@ import {TerminalHelpText, HelpTexts} from "./HelpText"; import {iTutorialNextStep, iTutorialSteps, ITutorial} from "./InteractiveTutorial"; import {showLiterature} from "./Literature"; -import {showMessage, Message} from "./Message"; +import { Message } from "./Message/Message"; +import { showMessage } from "./Message/MessageHelpers"; import {killWorkerScript, addWorkerScript} from "./NetscriptWorker"; import {Player} from "./Player"; import {hackWorldDaemon} from "./RedPill"; -import { findRunningScript, - RunningScript, - isScriptFilename } from "./Script"; -import {AllServers, GetServerByHostname, - getServer, Server} from "./Server"; +import { RunningScript } from "./Script/RunningScript"; +import { findRunningScript } from "./Script/ScriptHelpers"; +import { isScriptFilename } from "./Script/ScriptHelpersTS"; +import { AllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server"; +import { GetServerByHostname, + getServer, + getServerOnNetwork } from "./Server/ServerHelpers"; import {Settings} from "./Settings/Settings"; -import {SpecialServerIps, - SpecialServerNames} from "./SpecialServerIps"; +import { SpecialServerIps, + SpecialServerNames } from "./Server/SpecialServerIps"; import {getTextFile} from "./TextFile"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; import {containsAllStrings, @@ -1157,8 +1162,8 @@ let Terminal = { let ip = commandArray[1]; - for (var i = 0; i < Player.getCurrentServer().serversOnNetwork.length; i++) { - if (Player.getCurrentServer().getServerOnNetwork(i).ip == ip || Player.getCurrentServer().getServerOnNetwork(i).hostname == ip) { + for (var i = 0; i < s.serversOnNetwork.length; i++) { + if (getServerOnNetwork(s, i).ip == ip || getServerOnNetwork(s, i).hostname == ip) { Terminal.connectToServer(ip); return; } @@ -1812,11 +1817,13 @@ let Terminal = { postError("Incorrect usage of netstat/scan command. Usage: netstat/scan"); return; } - //Displays available network connections using TCP + + // Displays available network connections using TCP + const currServ = Player.getCurrentServer(); post("Hostname IP Root Access"); - for (let i = 0; i < Player.getCurrentServer().serversOnNetwork.length; i++) { + for (let i = 0; i < currServ.serversOnNetwork.length; i++) { //Add hostname - let entry = Player.getCurrentServer().getServerOnNetwork(i); + let entry = getServerOnNetwork(currServ, i); if (entry == null) { continue; } entry = entry.hostname; @@ -1824,16 +1831,16 @@ let Terminal = { let numSpaces = 21 - entry.length; let spaces = Array(numSpaces+1).join(" "); entry += spaces; - entry += Player.getCurrentServer().getServerOnNetwork(i).ip; + entry += getServerOnNetwork(currServ, i).ip; //Calculate padding and add root access info let hasRoot; - if (Player.getCurrentServer().getServerOnNetwork(i).hasAdminRights) { + if (getServerOnNetwork(currServ, i).hasAdminRights) { hasRoot = 'Y'; } else { hasRoot = 'N'; } - numSpaces = 21 - Player.getCurrentServer().getServerOnNetwork(i).ip.length; + numSpaces = 21 - getServerOnNetwork(currServ, i).ip.length; spaces = Array(numSpaces+1).join(" "); entry += spaces; entry += hasRoot; @@ -1867,7 +1874,7 @@ let Terminal = { visited[s.ip] = 1; } for (var i = s.serversOnNetwork.length-1; i >= 0; --i) { - stack.push(s.getServerOnNetwork(i)); + stack.push(getServerOnNetwork(s, i)); depthQueue.push(d+1); } if (d == 0) {continue;} //Don't print current server diff --git a/src/engine.js b/src/engine.js index 7ad1175f9..5051c707d 100644 --- a/src/engine.js +++ b/src/engine.js @@ -21,14 +21,12 @@ import {CompanyPositions} from "./Company/CompanyP import {initCompanies} from "./Company/Companies"; import { Corporation } from "./Corporation/Corporation"; import {CONSTANTS} from "./Constants"; - - import {createDevMenu, closeDevMenu} from "./DevMenu"; import { Factions, initFactions } from "./Faction/Factions"; import { displayFactionContent, joinFaction, processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers"; -import {FconfSettings} from "./Fconf"; +import { FconfSettings } from "./Fconf/FconfSettings"; import {displayLocationContent, initLocationButtons} from "./Location"; import {Locations} from "./Locations"; @@ -36,7 +34,7 @@ import {displayHacknetNodesContent, processAllHacknetNodeEarnings, updateHacknetNodesContent} from "./HacknetNode"; import {iTutorialStart} from "./InteractiveTutorial"; import {initLiterature} from "./Literature"; -import {checkForMessagesToSend, initMessages} from "./Message"; +import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers"; import {inMission, currMission} from "./Missions"; import {initSingularitySFFlags, hasSingularitySF, hasCorporationSF} from "./NetscriptFunctions"; @@ -54,13 +52,14 @@ import {saveObject, loadGame} from "./SaveObject"; import { getCurrentEditor, loadAllRunningScripts, scriptEditorInit, - updateScriptEditorContent } from "./Script"; -import {AllServers, Server, initForeignServers} from "./Server"; + updateScriptEditorContent } from "./Script/ScriptHelpers"; +import { AllServers } from "./Server/AllServers"; +import { Server } from "./Server/Server"; +import { initForeignServers } from "./Server/ServerHelpers"; import {Settings} from "./Settings/Settings"; import { initSourceFiles, SourceFiles } from "./SourceFile"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; - -import {SpecialServerIps, initSpecialServerIps} from "./SpecialServerIps"; +import {SpecialServerIps, initSpecialServerIps} from "./Server/SpecialServerIps"; import {StockMarket, StockSymbols, SymbolToStockMap, initStockSymbols, initSymbolToStockMap, stockMarketCycle, @@ -1317,7 +1316,7 @@ const Engine = { Engine.setDisplayElements(); //Sets variables for important DOM elements Engine.start(); //Run main game loop and Scripts loop Player.init(); - initForeignServers(); + initForeignServers(Player.getHomeComputer()); initCompanies(); initFactions(); initAugmentations(); diff --git a/utils/IPAddress.ts b/utils/IPAddress.ts index 691f27505..948b055eb 100644 --- a/utils/IPAddress.ts +++ b/utils/IPAddress.ts @@ -1,5 +1,5 @@ -import {AllServers} from "../src/Server"; -import {getRandomByte} from "./helpers/getRandomByte"; +import { AllServers } from "../src/Server/AllServers"; +import { getRandomByte } from "./helpers/getRandomByte"; /* Functions to deal with manipulating IP addresses*/ From bd13234f069f1cd1b52129b39d1a54d92ae25d87 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Mon, 4 Mar 2019 23:55:54 -0800 Subject: [PATCH 03/34] Corrected some incorrect imports --- src/CodingContractGenerator.js | 2 +- src/Player.js | 4 ++-- src/Prestige.js | 12 ++++++------ src/SaveObject.js | 4 ++-- src/Server/ServerPurchases.js | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/CodingContractGenerator.js b/src/CodingContractGenerator.js index 1538703bd..135d47863 100644 --- a/src/CodingContractGenerator.js +++ b/src/CodingContractGenerator.js @@ -3,7 +3,7 @@ import { CodingContract, CodingContractTypes } from "./CodingContracts"; import { Factions } from "./Faction/Factions"; import { Player } from "./Player"; -import { AllServers } from "./Server/Server"; +import { AllServers } from "./Server/AllServers"; import { GetServerByHostname } from "./Server/ServerHelpers"; import { getRandomInt } from "../utils/helpers/getRandomInt"; diff --git a/src/Player.js b/src/Player.js index a95234937..ca0c5c0ec 100644 --- a/src/Player.js +++ b/src/Player.js @@ -24,9 +24,9 @@ import {Gang, resetGangs} from "./Gang"; import {Locations} from "./Locations"; import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions"; import { Sleeve } from "./PersonObjects/Sleeve/Sleeve"; -import { AllServers } from "./Server/AllServers"; +import { AllServers, + AddToAllServers } from "./Server/AllServers"; import { Server } from "./Server/Server"; -import { AddToAllServers } from "./Server/ServerHelpers"; import {Settings} from "./Settings/Settings"; import {SpecialServerIps, SpecialServerNames} from "./Server/SpecialServerIps"; import {SourceFiles, applySourceFile} from "./SourceFile"; diff --git a/src/Prestige.js b/src/Prestige.js index 1b3ba168d..0f12c1e97 100755 --- a/src/Prestige.js +++ b/src/Prestige.js @@ -16,19 +16,19 @@ import { Factions, import { joinFaction } from "./Faction/FactionHelpers"; import {deleteGangDisplayContent} from "./Gang"; import {Locations} from "./Location"; +import { Message } from "./Message/Message"; import { initMessages, - Messages, - Message } from "./Message/MessageHelpers"; + Messages } from "./Message/MessageHelpers"; import {initSingularitySFFlags, hasWallStreetSF}from "./NetscriptFunctions"; import {WorkerScript, workerScripts, prestigeWorkerScripts} from "./NetscriptWorker"; import {Player} from "./Player"; -import { AllServers } from "./Server/AllServers"; +import { AllServers, + AddToAllServers, + prestigeAllServers } from "./Server/AllServers"; import { Server } from "./Server/Server" -import { AddToAllServers, - initForeignServers, - prestigeAllServers, +import { initForeignServers, prestigeHomeComputer } from "./Server/ServerHelpers"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; import { SpecialServerIps, diff --git a/src/SaveObject.js b/src/SaveObject.js index e59c0f777..27af3062a 100755 --- a/src/SaveObject.js +++ b/src/SaveObject.js @@ -14,8 +14,8 @@ import {processAllHacknetNodeEarnings} from "./HacknetNode"; import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers"; import {Player, loadPlayer} from "./Player"; import { loadAllRunningScripts } from "./Script/ScriptHelpers"; -import { AllServers } from "./Server/AllServers"; -import { loadAllServers } from "./Server/ServerHelpers"; +import { AllServers, + loadAllServers } from "./Server/AllServers"; import { Settings } from "./Settings/Settings"; import { loadSpecialServerIps, SpecialServerIps } from "./Server/SpecialServerIps"; diff --git a/src/Server/ServerPurchases.js b/src/Server/ServerPurchases.js index 163a725d1..7805206f3 100644 --- a/src/Server/ServerPurchases.js +++ b/src/Server/ServerPurchases.js @@ -5,9 +5,9 @@ import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { CONSTANTS } from "../Constants"; import { Player } from "../Player"; -import { AllServers } from "../Server/AllServers"; +import { AllServers, + AddToAllServers } from "../Server/AllServers"; import { Server } from "../Server/Server"; -import { AddToAllServers } from "../Server/ServerHelpers"; import { dialogBoxCreate } from "../../utils/DialogBox"; import { createRandomIp } from "../../utils/IPAddress"; import { yesNoTxtInpBoxGetInput } from "../../utils/YesNoBox"; From d9ee715bc75e46b410f87fd8a62103be20f44b74 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Tue, 5 Mar 2019 00:01:34 -0800 Subject: [PATCH 04/34] Fixed some more incorrect imports --- src/Location.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Location.js b/src/Location.js index 42caaa0ba..33281738f 100644 --- a/src/Location.js +++ b/src/Location.js @@ -11,9 +11,9 @@ import {beginInfiltration} from "./Infiltration"; import {hasBladeburnerSF} from "./NetscriptFunctions"; import {Locations} from "./Locations"; import {Player} from "./Player"; -import { AllServers } from "./Server/AllServers"; +import { AllServers, + AddToAllServers } from "./Server/AllServers"; import { Server } from "./Server/Server"; -import { AddToAllServers } from "./Server/ServerHelpers"; import { getPurchaseServerCost, purchaseServer, purchaseRamForHomeComputer } from "./Server/ServerPurchases"; From 58062bf421c65b0346189003afc9a7a166355743 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Wed, 13 Mar 2019 12:20:55 -0400 Subject: [PATCH 05/34] fix wrong location for function --- src/engine.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/engine.js b/src/engine.js index de21680f4..e85d4a65b 100644 --- a/src/engine.js +++ b/src/engine.js @@ -53,9 +53,8 @@ import { getCurrentEditor, loadAllRunningScripts, scriptEditorInit, updateScriptEditorContent } from "./Script/ScriptHelpers"; -import { AllServers } from "./Server/AllServers"; +import { AllServers, initForeignServers } from "./Server/AllServers"; import { Server } from "./Server/Server"; -import { initForeignServers } from "./Server/ServerHelpers"; import {Settings} from "./Settings/Settings"; import { initSourceFiles, SourceFiles } from "./SourceFile"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; From c378400a10ce52930387a0e8aa07d204ff145359 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Wed, 13 Mar 2019 14:01:42 -0400 Subject: [PATCH 06/34] fix initForeignServers import in Prestige.js --- src/Prestige.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Prestige.js b/src/Prestige.js index 0f12c1e97..28975ee2a 100755 --- a/src/Prestige.js +++ b/src/Prestige.js @@ -26,10 +26,10 @@ import {Player} from "./Player"; import { AllServers, AddToAllServers, - prestigeAllServers } from "./Server/AllServers"; + prestigeAllServers, + initForeignServers } from "./Server/AllServers"; import { Server } from "./Server/Server" -import { initForeignServers, - prestigeHomeComputer } from "./Server/ServerHelpers"; +import { prestigeHomeComputer } from "./Server/ServerHelpers"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; import { SpecialServerIps, SpecialServerIpsMap, From 59018d9902e09c92139c515af0b2ea079db736e4 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 00:25:08 -0400 Subject: [PATCH 07/34] Update AceNetscriptMode.js bladeburner getCurrentAction should be highlighted in text editor. --- src/ScriptEditor/AceNetscriptMode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScriptEditor/AceNetscriptMode.js b/src/ScriptEditor/AceNetscriptMode.js index dd41e93e3..eb522d0dd 100644 --- a/src/ScriptEditor/AceNetscriptMode.js +++ b/src/ScriptEditor/AceNetscriptMode.js @@ -119,7 +119,7 @@ let NetscriptFunctions = "getActionMaxLevel|getActionCurrentLevel|getActionAutolevel|" + "getActionRepGain|setActionAutolevel|setActionLevel|" + "getRank|getSkillPoints|getSkillLevel|getSkillUpgradeCost|" + - "upgradeSkill|getTeamSize|getCity|" + + "upgradeSkill|getTeamSize|getCity|getCurrentAction|" + "setTeamSize|getCityEstimatedPopulation|getCityEstimatedCommunities|" + "getCityChaos|switchCity|getStamina|joinBladeburnerFaction|getBonusTime|" + From cde2c9e615e92729516957437379f618f20d7a4f Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 00:49:54 -0400 Subject: [PATCH 08/34] Update getActionCountRemaining.rst Added description of what happens when getActionCountRemaining is used on a BlackOps --- doc/source/netscript/bladeburnerapi/getActionCountRemaining.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/netscript/bladeburnerapi/getActionCountRemaining.rst b/doc/source/netscript/bladeburnerapi/getActionCountRemaining.rst index af50a19d5..74e1c930d 100644 --- a/doc/source/netscript/bladeburnerapi/getActionCountRemaining.rst +++ b/doc/source/netscript/bladeburnerapi/getActionCountRemaining.rst @@ -10,3 +10,4 @@ getActionCountRemaining() Netscript Function Note that this is meant to be used for Contracts and Operations. This function will return 'Infinity' for actions such as Training and Field Analysis. + This function will return 1 for BlackOps not yet completed regardless of wether the player has the required rank to attempt the mission or not. From d4d5202cbdb7980d65f5898aea7c9c937aa3a946 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 01:04:08 -0400 Subject: [PATCH 09/34] Display integer time required in bladeburner contracts already display integer only, now ops and blackops do too. --- src/Bladeburner.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Bladeburner.js b/src/Bladeburner.js index d6ccbad22..818e62c8c 100644 --- a/src/Bladeburner.js +++ b/src/Bladeburner.js @@ -2505,7 +2505,7 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) { display:"inline-block", innerHTML:action.desc + "\n\n" + "Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" + - "Time Required (s): " + formatNumber(actionTime, 0) + "\n" + + "Time Required (s): " + formatNumber(Math.floor(actionTime), 0) + "\n" + "Contracts remaining: " + Math.floor(action.count) + "\n" + "Successes: " + action.successes + "\n" + "Failures: " + action.failures, @@ -2641,7 +2641,7 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) { display:"inline-block", innerHTML:action.desc + "\n\n" + "Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" + - "Time Required(s): " + formatNumber(actionTime, 1) + "\n" + + "Time Required(s): " + formatNumber(actionTime, 0) + "\n" + "Operations remaining: " + Math.floor(action.count) + "\n" + "Successes: " + action.successes + "\n" + "Failures: " + action.failures, @@ -2761,7 +2761,7 @@ Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) { el.appendChild(createElement("p", { display:"inline-block", innerHTML:"Estimated Success Chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" + - "Time Required(s): " + formatNumber(actionTime, 1), + "Time Required(s): " + formatNumber(actionTime, 0), })) } From 2198bfd9629d78585cbbf9a2945a44120818b35f Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 01:05:55 -0400 Subject: [PATCH 10/34] Update Bladeburner.js --- src/Bladeburner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bladeburner.js b/src/Bladeburner.js index 818e62c8c..63b43c5f0 100644 --- a/src/Bladeburner.js +++ b/src/Bladeburner.js @@ -2505,7 +2505,7 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) { display:"inline-block", innerHTML:action.desc + "\n\n" + "Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" + - "Time Required (s): " + formatNumber(Math.floor(actionTime), 0) + "\n" + + "Time Required (s): " + formatNumber(actionTime, 0) + "\n" + "Contracts remaining: " + Math.floor(action.count) + "\n" + "Successes: " + action.successes + "\n" + "Failures: " + action.failures, From 6969a8f92ce60f6cad4774bd69be4d9b33646636 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Wed, 13 Mar 2019 11:40:04 -0400 Subject: [PATCH 11/34] inactive buttons can no longer be highlighted, like bladeburner skill level up --- css/buttons.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/css/buttons.scss b/css/buttons.scss index 27fb845a9..b854356d6 100644 --- a/css/buttons.scss +++ b/css/buttons.scss @@ -47,6 +47,11 @@ button { border: 1px solid #333; cursor: default; + -moz-user-select: none; + -ms-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + &:hover { .tooltiptext, .tooltiptexthigh, From 3e10f5de6dcb6c7eb02915e511621918864ab03c Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 13 Mar 2019 15:17:30 -0700 Subject: [PATCH 12/34] Migrated Corporation UI implementation to use React --- .babelrc | 3 + css/companymanagement.scss | 22 +- doc/source/basicgameplay/stockmarket.rst | 5 + doc/source/changelog.rst | 6 +- doc/source/netscript.rst | 2 +- package-lock.json | 526 +- package.json | 7 + src/Constants.ts | 22 +- src/Corporation/Corporation.js | 5083 ----------------- src/Corporation/Corporation.jsx | 2343 ++++++++ src/Corporation/IndustryData.ts | 19 +- src/Corporation/Material.ts | 5 +- src/Corporation/Research.ts | 21 +- src/Corporation/ResearchTree.ts | 4 + src/Corporation/Warehouse.ts | 100 + src/Corporation/data/BaseResearchTree.ts | 36 +- src/Corporation/data/ResearchMetadata.ts | 45 +- src/Corporation/ui/BaseReactComponent.js | 22 + src/Corporation/ui/CityTabs.jsx | 56 + .../ui/CorporationUIEventHandler.js | 1478 +++++ src/Corporation/ui/HeaderTabs.jsx | 80 + src/Corporation/ui/Industry.jsx | 34 + src/Corporation/ui/IndustryOffice.jsx | 557 ++ src/Corporation/ui/IndustryOverview.jsx | 261 + src/Corporation/ui/IndustryWarehouse.jsx | 486 ++ src/Corporation/ui/LevelableUpgrade.jsx | 35 + src/Corporation/ui/MainPanel.jsx | 91 + src/Corporation/ui/Overview.jsx | 323 ++ src/Corporation/ui/Root.jsx | 17 + src/Corporation/ui/Routing.ts | 96 + src/Corporation/ui/UnlockUpgrade.jsx | 29 + src/NetscriptFunctions.js | 1 + src/Prestige.js | 4 +- src/ScriptEditor/CodeMirrorNetscriptMode.js | 1 + src/engine.js | 10 +- tsconfig.json | 1 + webpack.config.js | 10 +- 37 files changed, 6699 insertions(+), 5142 deletions(-) create mode 100644 .babelrc delete mode 100644 src/Corporation/Corporation.js create mode 100644 src/Corporation/Corporation.jsx create mode 100644 src/Corporation/Warehouse.ts create mode 100644 src/Corporation/ui/BaseReactComponent.js create mode 100644 src/Corporation/ui/CityTabs.jsx create mode 100644 src/Corporation/ui/CorporationUIEventHandler.js create mode 100644 src/Corporation/ui/HeaderTabs.jsx create mode 100644 src/Corporation/ui/Industry.jsx create mode 100644 src/Corporation/ui/IndustryOffice.jsx create mode 100644 src/Corporation/ui/IndustryOverview.jsx create mode 100644 src/Corporation/ui/IndustryWarehouse.jsx create mode 100644 src/Corporation/ui/LevelableUpgrade.jsx create mode 100644 src/Corporation/ui/MainPanel.jsx create mode 100644 src/Corporation/ui/Overview.jsx create mode 100644 src/Corporation/ui/Root.jsx create mode 100644 src/Corporation/ui/Routing.ts create mode 100644 src/Corporation/ui/UnlockUpgrade.jsx diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..7dd5e9df2 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-react"] +} diff --git a/css/companymanagement.scss b/css/companymanagement.scss index 8fd71c57c..5fde635a6 100644 --- a/css/companymanagement.scss +++ b/css/companymanagement.scss @@ -56,11 +56,13 @@ .cmpy-mgmt-industry-left-panel, .cmpy-mgmt-industry-right-panel { display: inline-block; - width: 45%; height: 100%; - top: 10px; overflow-y: auto; overflow-x: auto; + overflow: visible; + padding: 2px; + top: 10px; + width: 45%; } .cmpy-mgmt-industry-overview-panel { @@ -115,13 +117,18 @@ background-color: #333; } -/* Upgrades */ +/* Corporation Upgrades */ .cmpy-mgmt-upgrade-container { border: 1px solid #fff; width: 60%; margin: 4px; } +.cmpy-mgmt-upgrade-header { + margin: 6px; + padding: 6px; +} + .cmpy-mgmt-upgrade-div { display: inline-block; border: 1px solid #fff; @@ -136,10 +143,19 @@ background-color: #333; } +/* Industry Upgrades */ +.industry-purchases-and-upgrades-header { + font-size: 14px; + margin: 2px; + padding: 2px; +} + +/* Advertising */ .cmpy-mgmt-advertising-info { font-size: $defaultFontSize * 0.75; } +/* Research */ #corporation-research-popup-box-content { overflow-x: visible !important; } diff --git a/doc/source/basicgameplay/stockmarket.rst b/doc/source/basicgameplay/stockmarket.rst index eb28631ba..e61407b29 100644 --- a/doc/source/basicgameplay/stockmarket.rst +++ b/doc/source/basicgameplay/stockmarket.rst @@ -7,6 +7,11 @@ buy and sell stocks in order to make money. The WSE can be found in the 'City' tab, and is accessible in every city. +Automating the Stock Market +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +You can write scripts to perform automatic and algorithmic trading on the Stock Market. +See :ref:`netscript_tixapi` for more details. + Positions: Long vs Short ^^^^^^^^^^^^^^^^^^^^^^^^ When making a transaction on the stock market, there are two types of positions: diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index e6c945faf..ce6662453 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -6,9 +6,9 @@ Changelog v0.44.1 - 3/4/2019 ------------------ * Duplicate Sleeve changes: -** You can now purchase Augmentations for your Duplicate Sleeves -** Sleeves are now assigned to Shock Recovery task by default -** Shock Recovery and Synchronize tasks are now twice as effective + * You can now purchase Augmentations for your Duplicate Sleeves + * Sleeves are now assigned to Shock Recovery task by default + * Shock Recovery and Synchronize tasks are now twice as effective * Changed documentation so that Netscript functions are own their own pages. Sorry if this is annoying, it was necessary for properly cross-referencing * Officially deprecated the Wiki (the fandom site). Use the 'readthedocs' Documentation instead diff --git a/doc/source/netscript.rst b/doc/source/netscript.rst index 6b0c1b489..f2f1b9aa0 100644 --- a/doc/source/netscript.rst +++ b/doc/source/netscript.rst @@ -5,7 +5,7 @@ Netscript Netscript is the programming language used in the world of Bitburner. When you write scripts in Bitburner, they are written in the Netscript language. -Netscript is simply a subset of `JavaScript `_,. +Netscript is simply a subset of `JavaScript `_. This means that Netscript's syntax is identical to that of JavaScript, but it does not implement some of the features that JavaScript has. diff --git a/package-lock.json b/package-lock.json index f0c79f49b..0fc89cb0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,347 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "7.0.0" + } + }, + "@babel/core": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.3.4.tgz", + "integrity": "sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0", + "@babel/generator": "7.3.4", + "@babel/helpers": "7.3.1", + "@babel/parser": "7.3.4", + "@babel/template": "7.2.2", + "@babel/traverse": "7.3.4", + "@babel/types": "7.3.4", + "convert-source-map": "1.6.0", + "debug": "4.1.1", + "json5": "2.1.0", + "lodash": "4.17.11", + "resolve": "1.5.0", + "semver": "5.5.0", + "source-map": "0.5.7" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "2.1.1" + } + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "1.2.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.4.tgz", + "integrity": "sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==", + "dev": true, + "requires": { + "@babel/types": "7.3.4", + "jsesc": "2.5.2", + "lodash": "4.17.11", + "source-map": "0.5.7", + "trim-right": "1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-builder-react-jsx": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz", + "integrity": "sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==", + "dev": true, + "requires": { + "@babel/types": "7.3.4", + "esutils": "2.0.2" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "7.0.0", + "@babel/template": "7.2.2", + "@babel/types": "7.3.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "7.3.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "dev": true, + "requires": { + "@babel/types": "7.3.4" + } + }, + "@babel/helpers": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.3.1.tgz", + "integrity": "sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==", + "dev": true, + "requires": { + "@babel/template": "7.2.2", + "@babel/traverse": "7.3.4", + "@babel/types": "7.3.4" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "2.4.2", + "esutils": "2.0.2", + "js-tokens": "4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + } + } + }, + "@babel/parser": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", + "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==", + "dev": true + }, + "@babel/plugin-syntax-jsx": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz", + "integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", + "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", + "integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==", + "dev": true, + "requires": { + "@babel/helper-builder-react-jsx": "7.3.0", + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-syntax-jsx": "7.2.0" + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz", + "integrity": "sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-syntax-jsx": "7.2.0" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.2.0.tgz", + "integrity": "sha512-A32OkKTp4i5U6aE88GwwcuV4HAprUgHcTq0sSafLxjr6AW0QahrCRCjxogkbbcdtpbXkuTOlgpjophCxb6sh5g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-syntax-jsx": "7.2.0" + } + }, + "@babel/preset-react": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", + "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-transform-react-display-name": "7.2.0", + "@babel/plugin-transform-react-jsx": "7.3.0", + "@babel/plugin-transform-react-jsx-self": "7.2.0", + "@babel/plugin-transform-react-jsx-source": "7.2.0" + } + }, + "@babel/template": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0", + "@babel/parser": "7.3.4", + "@babel/types": "7.3.4" + } + }, + "@babel/traverse": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", + "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0", + "@babel/generator": "7.3.4", + "@babel/helper-function-name": "7.1.0", + "@babel/helper-split-export-declaration": "7.0.0", + "@babel/parser": "7.3.4", + "@babel/types": "7.3.4", + "debug": "4.1.1", + "globals": "11.3.0", + "lodash": "4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "2.1.1" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", + "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", + "dev": true, + "requires": { + "esutils": "2.0.2", + "lodash": "4.17.11", + "to-fast-properties": "2.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } + } + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -25,6 +366,28 @@ "resolved": "https://registry.npmjs.org/@types/numeral/-/numeral-0.0.25.tgz", "integrity": "sha512-ShHzHkYD+Ldw3eyttptCpUhF1/mkInWwasQkCNXZHOsJMJ/UMa8wXrxSrTJaVk0r4pLK/VnESVM0wFsfQzNEKQ==" }, + "@types/prop-types": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.0.tgz", + "integrity": "sha512-eItQyV43bj4rR3JPV0Skpl1SncRCdziTEK9/v8VwXmV6d/qOUO8/EuWeHBbCZcsfSHfzI5UyMJLCSXtxxznyZg==" + }, + "@types/react": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.6.tgz", + "integrity": "sha512-bN9qDjEMltmHrl0PZRI4IF2AbB7V5UlRfG+OOduckVnRQ4VzXVSzy/1eLAh778IEqhTnW0mmgL9yShfinNverA==", + "requires": { + "@types/prop-types": "15.7.0", + "csstype": "2.6.2" + } + }, + "@types/react-dom": { + "version": "16.8.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.2.tgz", + "integrity": "sha512-MX7n1wq3G/De15RGAAqnmidzhr2Y9O/ClxPxyqaNg96pGyeXUYPSvujgzEVpLo9oIP4Wn1UETl+rxTN02KEpBw==", + "requires": { + "@types/react": "16.8.6" + } + }, "@webassemblyjs/ast": { "version": "1.5.12", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.12.tgz", @@ -642,6 +1005,83 @@ "js-tokens": "3.0.2" } }, + "babel-loader": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.5.tgz", + "integrity": "sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw==", + "dev": true, + "requires": { + "find-cache-dir": "2.0.0", + "loader-utils": "1.1.0", + "mkdirp": "0.5.1", + "util.promisify": "1.0.0" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", + "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", + "dev": true, + "requires": { + "commondir": "1.0.1", + "make-dir": "1.2.0", + "pkg-dir": "3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "3.0.0", + "path-exists": "3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "2.2.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "3.0.0" + } + } + } + }, "bail": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", @@ -2034,6 +2474,15 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", @@ -2309,6 +2758,11 @@ } } }, + "csstype": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.2.tgz", + "integrity": "sha512-Rl7PvTae0pflc1YtxtKbiSqq20Ts6vpIYOD5WBafl4y123DyHUeLrRdQP66sQW8/6gmX8jrYJLXwNeMqYVJcow==" + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -5261,8 +5715,7 @@ "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { "version": "3.6.1", @@ -5940,6 +6393,14 @@ "integrity": "sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA==", "dev": true }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "3.0.2" + } + }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -6800,8 +7261,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -8192,6 +8652,16 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1", + "react-is": "16.8.3" + } + }, "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -8402,6 +8872,33 @@ "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", "dev": true }, + "react": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.3.tgz", + "integrity": "sha512-3UoSIsEq8yTJuSu0luO1QQWYbgGEILm+eJl2QN/VLDi7hL+EN18M3q3oVZwmVzzBJ3DkM7RMdRwBmZZ+b4IzSA==", + "requires": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1", + "prop-types": "15.7.2", + "scheduler": "0.13.3" + } + }, + "react-dom": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.3.tgz", + "integrity": "sha512-ttMem9yJL4/lpItZAQ2NTFAbV7frotHk5DZEHXUOws2rMmrsvh1Na7ThGT0dTzUIl6pqTOi5tYREfL8AEna3lA==", + "requires": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1", + "prop-types": "15.7.2", + "scheduler": "0.13.3" + } + }, + "react-is": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", + "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==" + }, "readable-stream": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", @@ -9179,6 +9676,15 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "scheduler": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz", + "integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==", + "requires": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1" + } + }, "schema-utils": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", @@ -10892,6 +11398,12 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -10962,6 +11474,12 @@ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, "trim-trailing-lines": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz", diff --git a/package.json b/package.json index c85190af6..94a86468a 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ }, "dependencies": { "@types/numeral": "0.0.25", + "@types/react": "^16.8.6", + "@types/react-dom": "^16.8.2", "acorn": "^5.0.0", "acorn-dynamic-import": "^2.0.0", "ajv": "^5.1.5", @@ -31,6 +33,8 @@ "memory-fs": "~0.4.1", "normalize.css": "^8.0.0", "numeral": "2.0.6", + "react": "^16.8.3", + "react-dom": "^16.8.3", "sprintf-js": "^1.1.1", "tapable": "^1.0.0", "uglifyjs-webpack-plugin": "^1.2.5", @@ -39,6 +43,9 @@ }, "description": "A cyberpunk-themed incremental game", "devDependencies": { + "@babel/core": "^7.3.4", + "@babel/preset-react": "^7.0.0", + "babel-loader": "^8.0.5", "beautify-lint": "^1.0.3", "benchmark": "^2.1.1", "bundle-loader": "~0.5.0", diff --git a/src/Constants.ts b/src/Constants.ts index ed1927d30..cb34aeb8a 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -281,17 +281,17 @@ export let CONSTANTS: IMap = { LatestUpdate: ` - v0.44.1 - * Duplicate Sleeve changes: - ** You can now purchase Augmentations for your Duplicate Sleeves - ** Sleeves are now assigned to Shock Recovery task by default - ** Shock Recovery and Synchronize tasks are now twice as effective - - * Changed documentation so that Netscript functions are own their own pages. Sorry if this is annoying, it was necessary for properly cross-referencing - * Officially deprecated the Wiki (the fandom site). Use the 'readthedocs' Documentation instead - * Bug Fix: 'rm' Terminal and Netscript commands now work on non-program files that have '.exe' in the name (by Github user MasonD) - * Bug Fix: The 'Find All Valid Math Expressions' Coding Contract should now properly ignore whitespace in answers - * Bug Fix: The 'Merge Overlapping Intervals' Coding Contract should now properly accept 2D arrays when being attempted through Netscript + v0.45.0 + * Corporation changes: + ** Decreased the time of a full market cycle from 15 seconds to 10 seconds. + ** This means that each Corporation 'state' will now only take 2 seconds, rather than 3 + ** Increased initial salaries for newly-hired employees + ** Increased the cost multiplier for upgrading office size (the cost will increase faster) + ** The stats of your employees now has a slightly larger effect on production & sales + ** Added several new Research upgrades + ** Reduced the amount of Scientific Research needed to unlock the Hi-Tech R&D Laboratory from 10k to 5k + ** Energy Material requirement of the Software industry reduced from 1 to 0.5 + ** Industries now have a maximum number of allowed products, starting at 3. This can be increased through research. ` } diff --git a/src/Corporation/Corporation.js b/src/Corporation/Corporation.js deleted file mode 100644 index 69708e7d3..000000000 --- a/src/Corporation/Corporation.js +++ /dev/null @@ -1,5083 +0,0 @@ -import { AllCorporationStates, - CorporationState } from "./CorporationState"; -import { CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades"; -import { CorporationUpgrades } from "./data/CorporationUpgrades"; -import { EmployeePositions } from "./EmployeePositions"; -import { Industries, - IndustryStartingCosts, - IndustryDescriptions, - IndustryResearchTrees } from "./IndustryData"; -import { IndustryUpgrades } from "./IndustryUpgrades"; -import { Material } from "./Material"; -import { MaterialSizes } from "./MaterialSizes"; -import { Product } from "./Product"; -import { ResearchMap } from "./ResearchMap"; - -import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; -import { CONSTANTS } from "../Constants"; -import { Factions } from "../Faction/Factions"; -import { showLiterature } from "../Literature"; -import { Locations } from "../Locations"; -import { Player } from "../Player"; - -import { numeralWrapper } from "../ui/numeralFormat"; -import { Page, routing } from "../ui/navigationTracking"; - -import { dialogBoxCreate } from "../../utils/DialogBox"; -import { clearSelector } from "../../utils/uiHelpers/clearSelector"; -import { Reviver, - Generic_toJSON, - Generic_fromJSON } from "../../utils/JSONReviver"; -import { appendLineBreaks } from "../../utils/uiHelpers/appendLineBreaks"; -import { createElement } from "../../utils/uiHelpers/createElement"; -import { createPopup } from "../../utils/uiHelpers/createPopup"; -import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton"; -import { formatNumber, generateRandomString } from "../../utils/StringHelperFunctions"; -import { getRandomInt } from "../../utils/helpers/getRandomInt"; -import { isString } from "../../utils/helpers/isString"; -import { KEY } from "../../utils/helpers/keyCodes"; -import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; -import { removeElement } from "../../utils/uiHelpers/removeElement"; -import { removeElementById } from "../../utils/uiHelpers/removeElementById"; -import { yesNoBoxCreate, - yesNoTxtInpBoxCreate, - yesNoBoxGetYesButton, - yesNoBoxGetNoButton, - yesNoTxtInpBoxGetYesButton, - yesNoTxtInpBoxGetNoButton, - yesNoTxtInpBoxGetInput, - yesNoBoxClose, - yesNoTxtInpBoxClose, - yesNoBoxOpen } from "../../utils/YesNoBox"; - -import Decimal from "decimal.js"; - -/* Constants */ -export const INITIALSHARES = 1e9; //Total number of shares you have at your company -export const SHARESPERPRICEUPDATE = 1e6; //When selling large number of shares, price is dynamically updated for every batch of this amount -export const IssueNewSharesCooldown = 216e3; // 12 Hour in terms of game cycles -export const SellSharesCooldown = 18e3; // 1 Hour in terms of game cycles - -export const CyclesPerMarketCycle = 75; -export const CyclesPerIndustryStateCycle = CyclesPerMarketCycle / AllCorporationStates.length; -export const SecsPerMarketCycle = CyclesPerMarketCycle / 5; - -export const Cities = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"]; - -export const WarehouseInitialCost = 5e9; //Initial purchase cost of warehouse -export const WarehouseInitialSize = 100; -export const WarehouseUpgradeBaseCost = 1e9; - -export const OfficeInitialCost = 4e9; -export const OfficeInitialSize = 3; -export const OfficeUpgradeBaseCost = 1e9; - -export const BribeThreshold = 100e12; //Money needed to be able to bribe for faction rep -export const BribeToRepRatio = 1e9; //Bribe Value divided by this = rep gain - -export const ProductProductionCostRatio = 5; //Ratio of material cost of a product to its production cost - -export const DividendMaxPercentage = 50; - -export const CyclesPerEmployeeRaise = 400; // All employees get a raise every X market cycles -export const EmployeeRaiseAmount = 50; // Employee salary increases by this (additive) - -// Delete Research Popup Box when clicking outside of it -$(document).mousedown(function(event) { - const boxId = "corporation-research-popup-box"; - const contentId = "corporation-research-popup-box-content"; - if (researchTreeBoxOpened) { - if ( $(event.target).closest("#" + contentId).get(0) == null ) { - // Delete the box - removeElement(researchTreeBox); - researchTreeBox = null; - researchTreeBoxOpened = false; - } - } -}); - -var empManualAssignmentModeActive = false; -function Industry(params={}) { - this.offices = { //Maps locations to offices. 0 if no office at that location - [Locations.Aevum]: 0, - [Locations.Chongqing]: 0, - [Locations.Sector12]: new OfficeSpace({ - loc:Locations.Sector12, - size:OfficeInitialSize, - }), - [Locations.NewTokyo]: 0, - [Locations.Ishima]: 0, - [Locations.Volhaven]: 0 - }; - - this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location - [Locations.Aevum]: 0, - [Locations.Chonqing]: 0, - [Locations.Sector12]: new Warehouse({ - loc:Locations.Sector12, - size: WarehouseInitialSize, - }), - [Locations.NewTokyo]: 0, - [Locations.Ishima]: 0, - [Locations.Volhaven]: 0 - }; - - this.name = params.name ? params.name : 0; - this.type = params.type ? params.type : 0; - - this.sciResearch = new Material({name: "Scientific Research"}); - this.researched = {}; // Object of acquired Research. Keys = research name - - //A map of the NAME of materials required to create produced materials to - //how many are needed to produce 1 unit of produced materials - this.reqMats = {}; - - //An array of the name of materials being produced - this.prodMats = []; - - this.products = {}; - this.makesProducts = false; - - this.awareness = 0; - this.popularity = 0; //Should always be less than awareness - this.startingCost = 0; - - /* The following are factors for how much production/other things are increased by - different factors. The production increase always has diminishing returns, - and they are all reprsented by exponentials of < 1 (e.g x ^ 0.5, x ^ 0.8) - The number for these represent the exponential. A lower number means more - diminishing returns */ - this.reFac = 0; //Real estate Factor - this.sciFac = 0; //Scientific Research Factor, affects quality - this.hwFac = 0; //Hardware factor - this.robFac = 0; //Robotics Factor - this.aiFac = 0; //AI Cores factor; - this.advFac = 0; //Advertising factor, affects sales - - this.prodMult = 0; //Production multiplier - - //Financials - this.lastCycleRevenue = new Decimal(0); - this.lastCycleExpenses = new Decimal(0); - this.thisCycleRevenue = new Decimal(0); - this.thisCycleExpenses = new Decimal(0); - - //Upgrades - var numUpgrades = Object.keys(IndustryUpgrades).length; - this.upgrades = Array(numUpgrades).fill(0); - - this.state = "START"; - this.newInd = true; - - this.init(); -} - -Industry.prototype.init = function() { - //Set the unique properties of an industry (how much its affected by real estate/scientific research, etc.) - this.startingCost = IndustryStartingCosts[this.type]; - switch (this.type) { - case Industries.Energy: - this.reFac = 0.65; - this.sciFac = 0.7; - this.robFac = 0.05; - this.aiFac = 0.3; - this.advFac = 0.08; - this.reqMats = { - "Hardware": 0.1, - "Metal": 0.2, - }; - this.prodMats = ["Energy"]; - break; - case Industries.Utilities: - case "Utilities": - this.reFac = 0.5; - this.sciFac = 0.6; - this.robFac = 0.4; - this.aiFac = 0.4; - this.advFac = 0.08; - this.reqMats = { - "Hardware": 0.1, - "Metal": 0.1, - } - this.prodMats = ["Water"]; - break; - case Industries.Agriculture: - this.reFac = 0.72; - this.sciFac = 0.5; - this.hwFac = 0.2; - this.robFac = 0.3; - this.aiFac = 0.3; - this.advFac = 0.04; - this.reqMats = { - "Water": 0.5, - "Energy": 0.5, - } - this.prodMats = ["Plants", "Food"]; - break; - case Industries.Fishing: - this.reFac = 0.15; - this.sciFac = 0.35; - this.hwFac = 0.35; - this.robFac = 0.5; - this.aiFac = 0.2; - this.advFac = 0.08; - this.reqMats = { - "Energy": 0.5, - } - this.prodMats = ["Food"]; - break; - case Industries.Mining: - this.reFac = 0.3; - this.sciFac = 0.26; - this.hwFac = 0.4; - this.robFac = 0.45; - this.aiFac = 0.45; - this.advFac = 0.06; - this.reqMats = { - "Energy": 0.8, - } - this.prodMats = ["Metal"]; - break; - case Industries.Food: - //reFac is unique for this bc it diminishes greatly per city. Handle this separately in code? - this.sciFac = 0.12; - this.hwFac = 0.15; - this.robFac = 0.3; - this.aiFac = 0.25; - this.advFac = 0.25; - this.reFac = 0.05; - this.reqMats = { - "Food": 0.5, - "Water": 0.5, - "Energy": 0.2, - } - this.makesProducts = true; - break; - case Industries.Tobacco: - this.reFac = 0.15; - this.sciFac = 0.75; - this.hwFac = 0.15; - this.robFac = 0.2; - this.aiFac = 0.15; - this.advFac = 0.2; - this.reqMats = { - "Plants": 1, - "Water": 0.2, - } - this.makesProducts = true; - break; - case Industries.Chemical: - this.reFac = 0.25; - this.sciFac = 0.75; - this.hwFac = 0.2; - this.robFac = 0.25; - this.aiFac = 0.2; - this.advFac = 0.07; - this.reqMats = { - "Plants": 1, - "Energy": 0.5, - "Water": 0.5, - } - this.prodMats = ["Chemicals"]; - break; - case Industries.Pharmaceutical: - this.reFac = 0.05; - this.sciFac = 0.8; - this.hwFac = 0.15; - this.robFac = 0.25; - this.aiFac = 0.2; - this.advFac = 0.16; - this.reqMats = { - "Chemicals": 2, - "Energy": 1, - "Water": 0.5, - } - this.prodMats = ["Drugs"]; - this.makesProducts = true; - break; - case Industries.Computer: - case "Computer": - this.reFac = 0.2; - this.sciFac = 0.62; - this.robFac = 0.36; - this.aiFac = 0.19; - this.advFac = 0.17; - this.reqMats = { - "Metal": 2, - "Energy": 1, - } - this.prodMats = ["Hardware"]; - this.makesProducts = true; - break; - case Industries.Robotics: - this.reFac = 0.32; - this.sciFac = 0.65; - this.aiFac = 0.36; - this.advFac = 0.18; - this.hwFac = 0.19; - this.reqMats = { - "Hardware": 5, - "Energy": 3, - } - this.prodMats = ["Robots"]; - this.makesProducts = true; - break; - case Industries.Software: - this.sciFac = 0.62; - this.advFac = 0.16; - this.hwFac = 0.25; - this.reFac = 0.1; - this.aiFac = 0.15; - this.robFac = 0.05; - this.reqMats = { - "Hardware": 0.5, - "Energy": 1, - } - this.prodMats = ["AICores"]; - this.makesProducts = true; - break; - case Industries.Healthcare: - this.reFac = 0.1; - this.sciFac = 0.75; - this.advFac = 0.11; - this.hwFac = 0.1; - this.robFac = 0.1; - this.aiFac = 0.1; - this.reqMats = { - "Robots": 10, - "AICores": 5, - "Energy": 5, - "Water": 5, - } - this.makesProducts = true; - break; - case Industries.RealEstate: - this.robFac = 0.6; - this.aiFac = 0.6; - this.advFac = 0.25; - this.sciFac = 0.05; - this.hwFac = 0.05; - this.reqMats = { - "Metal": 5, - "Energy": 5, - "Water": 2, - "Hardware": 4 - } - this.prodMats = ["RealEstate"]; - this.makesProducts = true; - break; - default: - console.log("ERR: Invalid Industry Type passed into Industry.init(): " + this.type); - return; - } -} - -Industry.prototype.getProductDescriptionText = function() { - if (!this.makesProducts) {return;} - switch (this.type) { - case Industries.Food: - return "create and manage restaurants"; - break; - case Industries.Tobacco: - return "create tobacco and tobacco-related products"; - break; - case Industries.Pharmaceutical: - return "develop new pharmaceutical drugs"; - break; - case Industries.Computer: - case "Computer": - return "create new computer hardware and networking infrastructures"; - break; - case Industries.Robotics: - return "build specialized robots and robot-related products"; - break; - case Industries.Software: - return "develop computer software"; - break; - case Industries.Healthcare: - return "build and manage hospitals"; - break; - case Industries.RealEstate: - return "develop and manage real estate properties"; - break; - default: - console.log("ERROR: Invalid industry type in Industry.getProductDescriptionText"); - return ""; - } -} - -//Calculates the values that factor into the production and properties of -//materials/products (such as quality, etc.) -Industry.prototype.calculateProductionFactors = function() { - var multSum = 0; - for (var i = 0; i < Cities.length; ++i) { - var city = Cities[i]; - var warehouse = this.warehouses[city]; - if (!(warehouse instanceof Warehouse)) { - continue; - } - - var materials = warehouse.materials, - office = this.offices[city]; - - var cityMult = Math.pow(0.002 * materials.RealEstate.qty+1, this.reFac) * - Math.pow(0.002 * materials.Hardware.qty+1, this.hwFac) * - Math.pow(0.002 * materials.Robots.qty+1, this.robFac) * - Math.pow(0.002 * materials.AICores.qty+1, this.aiFac); - multSum += Math.pow(cityMult, 0.73); - } - - multSum < 1 ? this.prodMult = 1 : this.prodMult = multSum; -} - -Industry.prototype.updateWarehouseSizeUsed = function(warehouse) { - if (warehouse instanceof Warehouse) { - //This resets the size back to 0 and then accounts for materials - warehouse.updateMaterialSizeUsed(); - } - - for (var prodName in this.products) { - if (this.products.hasOwnProperty(prodName)) { - var prod = this.products[prodName]; - warehouse.sizeUsed += (prod.data[warehouse.loc][0] * prod.siz); - if (prod.data[warehouse.loc][0] > 0) { - warehouse.breakdown += (prodName + ": " + formatNumber(prod.data[warehouse.loc][0] * prod.siz, 0) + "
"); - } - } - } -} - -Industry.prototype.process = function(marketCycles=1, state, company) { - this.state = state; - - //At the start of a cycle, store and reset revenue/expenses - //Then calculate salaries and processs the markets - if (state === "START") { - if (isNaN(this.thisCycleRevenue) || isNaN(this.thisCycleExpenses)) { - console.log("ERROR: NaN in Corporation's computed revenue/expenses"); - console.log(this.thisCycleRevenue.toString()); - console.log(this.thisCycleExpenses.toString()); - dialogBoxCreate("Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer"); - this.thisCycleRevenue = new Decimal(0); - this.thisCycleExpenses = new Decimal(0); - } - this.lastCycleRevenue = this.thisCycleRevenue.dividedBy(marketCycles * SecsPerMarketCycle); - this.lastCycleExpenses = this.thisCycleExpenses.dividedBy(marketCycles * SecsPerMarketCycle); - this.thisCycleRevenue = new Decimal(0); - this.thisCycleExpenses = new Decimal(0); - - //Once you start making revenue, the player should no longer be - //considered new, and therefore no longer needs the 'tutorial' UI elements - if (this.lastCycleRevenue.gt(0)) {this.newInd = false;} - - //Process offices (and the employees in them) - var employeeSalary = 0; - for (var officeLoc in this.offices) { - if (this.offices.hasOwnProperty(officeLoc) && - this.offices[officeLoc] instanceof OfficeSpace) { - employeeSalary += this.offices[officeLoc].process(marketCycles, {industry:this, corporation:company}); - } - } - this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary); - - //Process change in demand/competition of materials/products - this.processMaterialMarket(marketCycles); - this.processProductMarket(marketCycles); - - //Process loss of popularity - this.popularity -= (marketCycles * .0001); - this.popularity = Math.max(0, this.popularity); - - //Process Dreamsense gains - var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4; - if (popularityGain > 0) { - this.popularity += (popularityGain * marketCycles); - this.awareness += (awarenessGain * marketCycles); - } - - return; - } - - //Process production, purchase, and import/export of materials - var res = this.processMaterials(marketCycles, company); - this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); - this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); - - //Process creation, production & sale of products - res = this.processProducts(marketCycles, company); - this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); - this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); - -} - -//Process change in demand and competition for this industry's materials -Industry.prototype.processMaterialMarket = function(marketCycles=1) { - //References to prodMats and reqMats - var reqMats = this.reqMats, prodMats = this.prodMats; - - //Only 'process the market' for materials that this industry deals with - for (var i = 0; i < Cities.length; ++i) { - //If this industry has a warehouse in this city, process the market - //for every material this industry requires or produces - if (this.warehouses[Cities[i]] instanceof Warehouse) { - var wh = this.warehouses[Cities[i]]; - for (var name in reqMats) { - if (reqMats.hasOwnProperty(name)) { - wh.materials[name].processMarket(); - } - } - - //Produced materials are stored in an array - for (var foo = 0; foo < prodMats.length; ++foo) { - wh.materials[prodMats[foo]].processMarket(); - } - - //Process these twice because these boost production - wh.materials["Hardware"].processMarket(); - wh.materials["Robots"].processMarket(); - wh.materials["AICores"].processMarket(); - wh.materials["RealEstate"].processMarket(); - } - } -} - -//Process change in demand and competition for this industry's products -Industry.prototype.processProductMarket = function(marketCycles=1) { - //Demand gradually decreases, and competition gradually increases - for (var name in this.products) { - if (this.products.hasOwnProperty(name)) { - var product = this.products[name]; - var change = getRandomInt(1, 3) * 0.0004; - if (this.type === Industries.Pharmaceutical || this.type === Industries.Software || - this.type === Industries.Robotics) { - change *= 3; - } - change *= marketCycles; - product.dmd -= change; - product.cmp += change; - product.cmp = Math.min(product.cmp, 99.99); - product.dmd = Math.max(product.dmd, 0.001); - } - } -} - -//Process production, purchase, and import/export of materials -Industry.prototype.processMaterials = function(marketCycles=1, company) { - var revenue = 0, expenses = 0, industry = this; - this.calculateProductionFactors(); - - //At the start of the export state, set the imports of everything to 0 - if (this.state === "EXPORT") { - for (var i = 0; i < Cities.length; ++i) { - var city = Cities[i], office = this.offices[city]; - if (!(this.warehouses[city] instanceof Warehouse)) { - continue; - } - var warehouse = this.warehouses[city]; - for (var matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - var mat = warehouse.materials[matName]; - mat.imp = 0; - } - } - } - } - - for (var i = 0; i < Cities.length; ++i) { - var city = Cities[i], office = this.offices[city]; - - if (this.warehouses[city] instanceof Warehouse) { - var warehouse = this.warehouses[city]; - - switch(this.state) { - - case "PURCHASE": - /* Process purchase of materials */ - for (var matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - (function(matName, ind) { - var mat = warehouse.materials[matName]; - var buyAmt, maxAmt; - if (warehouse.smartSupplyEnabled && Object.keys(ind.reqMats).includes(matName)) { - //Smart supply tracker is stored as per second rate - mat.buy = ind.reqMats[matName] * warehouse.smartSupplyStore; - buyAmt = mat.buy * SecsPerMarketCycle * marketCycles; - } else { - buyAmt = (mat.buy * SecsPerMarketCycle * marketCycles); - } - - if (matName == "RealEstate") { - maxAmt = buyAmt; - } else { - maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]); - } - var buyAmt = Math.min(buyAmt, maxAmt); - if (buyAmt > 0) { - mat.qty += buyAmt; - expenses += (buyAmt * mat.bCost); - } - })(matName, industry); - this.updateWarehouseSizeUsed(warehouse); - } - } //End process purchase of materials - break; - - case "PRODUCTION": - warehouse.smartSupplyStore = 0; //Reset smart supply amount - - /* Process production of materials */ - if (this.prodMats.length > 0) { - var mat = warehouse.materials[this.prodMats[0]]; - //Calculate the maximum production of this material based - //on the office's productivity - var maxProd = this.getOfficeProductivity(office) - * this.prodMult // Multiplier from materials - * company.getProductionMultiplier() - * this.getProductionMultiplier(); // Multiplier from Research - let prod; - - if (mat.prdman[0]) { - //Production is manually limited - prod = Math.min(maxProd, mat.prdman[1]); - } else { - prod = maxProd; - } - prod *= (SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle - //Calculate net change in warehouse storage making - //the produced materials will cost - var totalMatSize = 0; - for (var tmp = 0; tmp < this.prodMats.length; ++tmp) { - totalMatSize += (MaterialSizes[this.prodMats[tmp]]); - } - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - var normQty = this.reqMats[reqMatName]; - totalMatSize -= (MaterialSizes[reqMatName] * normQty); - } - } - //If not enough space in warehouse, limit the amount of produced materials - if (totalMatSize > 0) { - var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize); - prod = Math.min(maxAmt, prod); - } - - if (prod < 0) {prod = 0;} - - //Keep track of production for smart supply (/s) - warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles)); - - //Make sure we have enough resource to make our materials - var producableFrac = 1; - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - var req = this.reqMats[reqMatName] * prod; - if (warehouse.materials[reqMatName].qty < req) { - producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); - } - } - } - if (producableFrac <= 0) {producableFrac = 0; prod = 0;} - - //Make our materials if they are producable - if (producableFrac > 0 && prod > 0) { - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac); - warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; - warehouse.materials[reqMatName].prd = 0; - warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles); - } - } - for (var j = 0; j < this.prodMats.length; ++j) { - warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac); - warehouse.materials[this.prodMats[j]].qlt = - (office.employeeProd[EmployeePositions.Engineer] / 100 + - Math.pow(this.sciResearch.qty, this.sciFac) + - Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3); - } - } else { - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - warehouse.materials[reqMatName].prd = 0; - } - } - } - - //Per second - var fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles); - for (var fooI = 0; fooI < this.prodMats.length; ++fooI) { - warehouse.materials[this.prodMats[fooI]].prd = fooProd; - } - } else { - //If this doesn't produce any materials, then it only creates - //Products. Creating products will consume materials. The - //Production of all consumed materials must be set to 0 - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - warehouse.materials[reqMatName].prd = 0; - } - } - } - break; - - case "SALE": - /* Process sale of materials */ - for (var matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - var mat = warehouse.materials[matName]; - if (mat.sCost < 0 || mat.sllman[0] === false) { - mat.sll = 0; - continue; - } - var mat = warehouse.materials[matName]; - - var sCost; - if (isString(mat.sCost)) { - sCost = mat.sCost.replace(/MP/g, mat.bCost); - sCost = eval(sCost); - } else { - sCost = mat.sCost; - } - - //Calculate how much of the material sells (per second) - let markup = 1, markupLimit = mat.getMarkupLimit(); - if (sCost > mat.bCost) { - //Penalty if difference between sCost and bCost is greater than markup limit - if ((sCost - mat.bCost) > markupLimit) { - markup = Math.pow(markupLimit / (sCost - mat.bCost), 2); - } - } else if (sCost < mat.bCost) { - if (sCost <= 0) { - markup = 1e12; //Sell everything, essentially discard - } else { - //Lower prices than market increases sales - markup = mat.bCost / sCost; - } - } - //var businessFactor = 1 + (office.employeeProd[EmployeePositions.Business] / office.employeeProd["total"]); - var businessFactor = this.getBusinessFactor(office); //Business employee productivity - var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity - var marketFactor = this.getMarketFactor(mat); //Competition + demand - var maxSell = (mat.qlt + .001) - * marketFactor - * markup - * businessFactor - * company.getSalesMultiplier() - * advertisingFactor - * this.getSalesMultiplier(); - var sellAmt; - if (isString(mat.sllman[1])) { - //Dynamically evaluated - var tmp = mat.sllman[1].replace(/MAX/g, maxSell); - tmp = tmp.replace(/PROD/g, mat.prd); - try { - sellAmt = eval(tmp); - } catch(e) { - dialogBoxCreate("Error evaluating your sell amount for material " + mat.name + - " in " + this.name + "'s " + city + " office. The sell amount " + - "is being set to zero"); - sellAmt = 0; - } - sellAmt = Math.min(maxSell, sellAmt); - } else if (mat.sllman[1] === -1) { - //Backwards compatibility, -1 = MAX - sellAmt = maxSell; - } else { - //Player's input value is just a number - sellAmt = Math.min(maxSell, mat.sllman[1]); - } - - sellAmt = (sellAmt * SecsPerMarketCycle * marketCycles); - sellAmt = Math.min(mat.qty, sellAmt); - if (sellAmt < 0) { - console.log("sellAmt calculated to be negative"); - mat.sll = 0; - continue; - } - if (sellAmt && sCost >= 0) { - mat.qty -= sellAmt; - revenue += (sellAmt * sCost); - mat.sll = sellAmt / (SecsPerMarketCycle * marketCycles); - } else { - mat.sll = 0; - } - } - } //End processing of sale of materials - break; - - case "EXPORT": - for (var matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - var mat = warehouse.materials[matName]; - mat.totalExp = 0; //Reset export - for (var expI = 0; expI < mat.exp.length; ++expI) { - var exp = mat.exp[expI]; - var amt = exp.amt.replace(/MAX/g, mat.qty / (SecsPerMarketCycle * marketCycles)); - try { - amt = eval(amt); - } catch(e) { - dialogBoxCreate("Calculating export for " + mat.name + " in " + - this.name + "'s " + city + " division failed with " + - "error: " + e); - continue; - } - if (isNaN(amt)) { - dialogBoxCreate("Error calculating export amount for " + mat.name + " in " + - this.name + "'s " + city + " division."); - continue; - } - amt = amt * SecsPerMarketCycle * marketCycles; - - if (mat.qty < amt) { - amt = mat.qty; - } - if (amt === 0) { - break; //None left - } - for (var foo = 0; foo < company.divisions.length; ++foo) { - if (company.divisions[foo].name === exp.ind) { - var expIndustry = company.divisions[foo]; - var expWarehouse = expIndustry.warehouses[exp.city]; - if (!(expWarehouse instanceof Warehouse)) { - console.log("ERROR: Invalid export! " + expIndustry.name + " " + exp.city); - break; - } - - //Make sure theres enough space in warehouse - if (expWarehouse.sizeUsed >= expWarehouse.size) { - return; //Warehouse at capacity - } else { - var maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]); - amt = Math.min(maxAmt, amt); - } - expWarehouse.materials[matName].imp += (amt / (SecsPerMarketCycle * marketCycles)); - expWarehouse.materials[matName].qty += amt; - expWarehouse.materials[matName].qlt = mat.qlt; - mat.qty -= amt; - mat.totalExp += amt; - expIndustry.updateWarehouseSizeUsed(expWarehouse); - break; - } - } - } - //totalExp should be per second - mat.totalExp /= (SecsPerMarketCycle * marketCycles); - } - } - - break; - - case "START": - break; - default: - console.log("ERROR: Invalid state: " + this.state); - break; - } //End switch(this.state) - this.updateWarehouseSizeUsed(warehouse); - - } // End warehouse - - //Produce Scientific Research based on R&D employees - //Scientific Research can be produced without a warehouse - if (office instanceof OfficeSpace) { - this.sciResearch.qty += (.005 - * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5) - * company.getScientificResearchMultiplier() - * this.getScientificResearchMultiplier()); - } - } - return [revenue, expenses]; -} - -//Process production & sale of this industry's FINISHED products (including all of their stats) -Industry.prototype.processProducts = function(marketCycles=1, corporation) { - var revenue = 0, expenses = 0; - - //Create products - if (this.state === "PRODUCTION") { - for (var prodName in this.products) { - if (this.products.hasOwnProperty(prodName)) { - var prod = this.products[prodName]; - if (!prod.fin) { - var city = prod.createCity, office = this.offices[city]; - var total = office.employeeProd[EmployeePositions.Operations] + - office.employeeProd[EmployeePositions.Engineer] + - office.employeeProd[EmployeePositions.Management], ratio; - if (total === 0) { - ratio = 0; - } else { - ratio = office.employeeProd[EmployeePositions.Engineer] / total + - office.employeeProd[EmployeePositions.Operations] / total + - office.employeeProd[EmployeePositions.Management] / total; - } - prod.createProduct(marketCycles, ratio * Math.pow(total, 0.29)); - if (prod.prog >= 100) { - prod.finishProduct(office.employeeProd, this); - } - break; - } - } - } - } - - //Produce Products - for (var prodName in this.products) { - if (this.products.hasOwnProperty(prodName)) { - var prod = this.products[prodName]; - if (prod instanceof Product && prod.fin) { - revenue += this.processProduct(marketCycles, prod, corporation); - } - } - } - return [revenue, expenses]; -} - -//Processes FINISHED products -Industry.prototype.processProduct = function(marketCycles=1, product, corporation) { - var totalProfit = 0; - for (var i = 0; i < Cities.length; ++i) { - var city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city]; - if (warehouse instanceof Warehouse) { - switch(this.state) { - - case "PRODUCTION": - //Calculate the maximum production of this material based - //on the office's productivity - var maxProd = this.getOfficeProductivity(office, {forProduct:true}) - * corporation.getProductionMultiplier() - * this.prodMult // Multiplier from materials - * this.getProductionMultiplier(); // Multiplier from research - let prod; - - //Account for whether production is manually limited - if (product.prdman[city][0]) { - prod = Math.min(maxProd, product.prdman[city][1]); - } else { - prod = maxProd; - } - prod *= (SecsPerMarketCycle * marketCycles); - - //Calculate net change in warehouse storage making the Products will cost - var netStorageSize = product.siz; - for (var reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - var normQty = product.reqMats[reqMatName]; - netStorageSize -= (MaterialSizes[reqMatName] * normQty); - } - } - - //If there's not enough space in warehouse, limit the amount of Product - if (netStorageSize > 0) { - var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / netStorageSize); - prod = Math.min(maxAmt, prod); - } - - warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles)); - - //Make sure we have enough resources to make our Products - var producableFrac = 1; - for (var reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - var req = product.reqMats[reqMatName] * prod; - if (warehouse.materials[reqMatName].qty < req) { - producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); - } - } - } - - //Make our Products if they are producable - if (producableFrac > 0 && prod > 0) { - for (var reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - var reqMatQtyNeeded = (product.reqMats[reqMatName] * prod * producableFrac); - warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; - warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles); - } - } - //Quantity - product.data[city][0] += (prod * producableFrac); - } - - //Keep track of production Per second - product.data[city][1] = prod * producableFrac / (SecsPerMarketCycle * marketCycles); - break; - - case "SALE": - //Process sale of Products - product.pCost = 0; //Estimated production cost - for (var reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - product.pCost += (product.reqMats[reqMatName] * warehouse.materials[reqMatName].bCost); - } - } - - //Since its a product, its production cost is increased for labor - product.pCost *= ProductProductionCostRatio; - - //Calculate Sale Cost (sCost), which could be dynamically evaluated - var sCost; - if (isString(product.sCost)) { - sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku); - sCost = eval(sCost); - } else { - sCost = product.sCost; - } - - var markup = 1, markupLimit = product.rat / product.mku; - if (sCost > product.pCost) { - if ((sCost - product.pCost) > markupLimit) { - markup = markupLimit / (sCost - product.pCost); - } - } - var businessFactor = this.getBusinessFactor(office); //Business employee productivity - var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity - var marketFactor = this.getMarketFactor(product); //Competition + demand - var maxSell = 0.5 - * Math.pow(product.rat, 0.65) - * marketFactor - * corporation.getSalesMultiplier() - * Math.pow(markup, 2) - * businessFactor - * advertisingFactor - * this.getSalesMultiplier(); - var sellAmt; - if (product.sllman[city][0] && isString(product.sllman[city][1])) { - //Sell amount is dynamically evaluated - var tmp = product.sllman[city][1].replace(/MAX/g, maxSell); - tmp = tmp.replace(/PROD/g, product.data[city][1]); - try { - tmp = eval(tmp); - } catch(e) { - dialogBoxCreate("Error evaluating your sell price expression for " + product.name + - " in " + this.name + "'s " + city + " office. Sell price is being set to MAX"); - tmp = maxSell; - } - sellAmt = Math.min(maxSell, tmp); - } else if (product.sllman[city][0] && product.sllman[city][1] > 0) { - //Sell amount is manually limited - sellAmt = Math.min(maxSell, product.sllman[city][1]); - } else { - //Backwards compatibility, -1 = 0 - sellAmt = maxSell; - } - if (sellAmt < 0) { sellAmt = 0; } - sellAmt = sellAmt * SecsPerMarketCycle * marketCycles; - sellAmt = Math.min(product.data[city][0], sellAmt); //data[0] is qty - if (sellAmt && sCost) { - product.data[city][0] -= sellAmt; //data[0] is qty - totalProfit += (sellAmt * sCost); - product.data[city][2] = sellAmt / (SecsPerMarketCycle * marketCycles); //data[2] is sell property - } else { - product.data[city][2] = 0; //data[2] is sell property - } - break; - - case "START": - case "PURCHASE": - case "EXPORT": - break; - default: - console.log("ERROR: Invalid State: " + this.state); - break; - } //End switch(this.state) - } - } - return totalProfit; -} - -Industry.prototype.discontinueProduct = function(product, parentRefs) { - var company = parentRefs.company, industry = parentRefs.industry; - for (var productName in this.products) { - if (this.products.hasOwnProperty(productName)) { - if (product === this.products[productName]) { - delete this.products[productName]; - company.updateUIContent(); - } - } - } -} - -Industry.prototype.upgrade = function(upgrade, refs) { - var corporation = refs.corporation, division = refs.division, - office = refs.office; - var upgN = upgrade[0], basePrice = upgrade[1], priceMult = upgrade[2], - upgradeBenefit = upgrade[3]; - while (this.upgrades.length <= upgN) {this.upgrades.push(0);} - ++this.upgrades[upgN]; - - switch (upgN) { - case 0: //Coffee, 5% energy per employee - for (let i = 0; i < office.employees.length; ++i) { - office.employees[i].ene = Math.min(office.employees[i].ene * 1.05, office.maxEne); - } - break; - case 1: //AdVert.Inc, - var advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier(); - this.awareness += (3 * advMult); - this.popularity += (1 * advMult); - this.awareness *= (1.01 * advMult); - this.popularity *= ((1 + getRandomInt(1, 3) / 100) * advMult); - break; - default: - console.log("ERROR: Un-implemented function index: " + upgN); - break; - } -} - -//Returns how much of a material can be produced based of office productivity (employee stats) -Industry.prototype.getOfficeProductivity = function(office, params) { - var total = office.employeeProd[EmployeePositions.Operations] + - office.employeeProd[EmployeePositions.Engineer] + - office.employeeProd[EmployeePositions.Management], ratio; - if (total === 0) { - ratio = 0; - } else { - ratio = (office.employeeProd[EmployeePositions.Operations] / total) * - (office.employeeProd[EmployeePositions.Engineer] / total) * - (office.employeeProd[EmployeePositions.Management] / total); - ratio = Math.max(0.01, ratio); //Minimum ratio value if you have employees - } - if (params && params.forProduct) { - return ratio * Math.pow(total, 0.22); - } else { - return 2 * ratio * Math.pow(total, 0.3); - } -} - -//Returns a multiplier based on the office' 'Business' employees that affects sales -Industry.prototype.getBusinessFactor = function(office) { - var ratioMult = 1; - if (office.employeeProd["total"] > 0) { - ratioMult = 1 + (office.employeeProd[EmployeePositions.Business] / office.employeeProd["total"]); - } - return ratioMult * Math.pow(1 + office.employeeProd[EmployeePositions.Business], 0.15); -} - -//Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This -//multiplier affects sales. The result is: -// [Total sales mult, total awareness mult, total pop mult, awareness/pop ratio mult] -Industry.prototype.getAdvertisingFactors = function() { - var awarenessFac = Math.pow(this.awareness + 1, this.advFac); - var popularityFac = Math.pow(this.popularity + 1, this.advFac); - var ratioFac = (this.awareness === 0 ? 0.01 : Math.max((this.popularity + .001) / this.awareness, 0.01)); - var totalFac = Math.pow(awarenessFac * popularityFac * ratioFac, 0.85); - return [totalFac, awarenessFac, popularityFac, ratioFac]; -} - -//Returns a multiplier based on a materials demand and competition that affects sales -Industry.prototype.getMarketFactor = function(mat) { - return mat.dmd * (100 - mat.cmp)/100; -} - -// Returns a boolean indicating whether this Industry has the specified Research -Industry.prototype.hasResearch = function(name) { - return (this.researched[name] === true); -} - -Industry.prototype.updateResearchTree = function() { - const researchTree = IndustryResearchTrees[this.type]; - - // Since ResearchTree data isnt saved, we'll update the Research Tree data - // based on the stored 'researched' property in the Industry object - if (Object.keys(researchTree.researched).length !== Object.keys(this.researched).length) { - console.log("Updating Corporation Research Tree Data"); - for (let research in this.researched) { - researchTree.research(research); - } - } -} - -// Get multipliers from Research -Industry.prototype.getAdvertisingMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getAdvertisingMultiplier(); -} - -Industry.prototype.getEmployeeChaMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getEmployeeChaMultiplier(); -} - -Industry.prototype.getEmployeeCreMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getEmployeeCreMultiplier(); -} - -Industry.prototype.getEmployeeEffMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getEmployeeEffMultiplier(); -} - -Industry.prototype.getEmployeeIntMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getEmployeeIntMultiplier(); -} - -Industry.prototype.getProductionMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getProductionMultiplier(); -} - -Industry.prototype.getSalesMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getSalesMultiplier(); -} - -Industry.prototype.getScientificResearchMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getScientificResearchMultiplier(); -} - -Industry.prototype.getStorageMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getStorageMultiplier(); -} - -// Create the Research Tree UI for this Industry -Industry.prototype.createResearchBox = function() { - const boxId = "corporation-research-popup-box"; - - if (researchTreeBoxOpened) { - // It's already opened, so delete it to refresh content - removeElementById(boxId); - researchTreeBox = null; - } - - this.updateResearchTree(); - const researchTree = IndustryResearchTrees[this.type]; - - - // Create the popup first, so that the tree diagram can be added to it - // This is handled by Treant - researchTreeBox = createPopup(boxId, [], { backgroundColor: "black" }); - - // Get the tree's markup (i.e. config) for Treant - const markup = researchTree.createTreantMarkup(); - markup.chart.container = "#" + boxId + "-content"; - markup.chart.nodeAlign = "BOTTOM"; - markup.chart.rootOrientation = "WEST"; - markup.chart.siblingSeparation = 40; - markup.chart.connectors = { - type: "step", - style: { - "arrow-end": "block-wide-long", - "stroke": "white", - "stroke-width": 2, - }, - } - - // Construct the tree with Treant - const treantTree = new Treant(markup); - - // Add Event Listeners for all Nodes - const allResearch = researchTree.getAllNodes(); - for (let i = 0; i < allResearch.length; ++i) { - // If this is already Researched, skip it - if (this.researched[allResearch[i]] === true) { - continue; - } - - // Get the Research object - const research = ResearchMap[allResearch[i]]; - - // Get the DOM Element to add a click listener to it - const sanitizedName = allResearch[i].replace(/\s/g, ''); - const div = document.getElementById(sanitizedName + "-corp-research-click-listener"); - if (div == null) { - console.warn(`Could not find Research Tree div for ${sanitizedName}`); - continue; - } - - div.addEventListener("click", () => { - if (this.sciResearch.qty >= research.cost) { - this.sciResearch.qty -= research.cost; - - // Get the Node from the Research Tree and set its 'researched' property - researchTree.research(allResearch[i]); - this.researched[allResearch[i]] = true; - - return this.createResearchBox(); - } else { - dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`); - } - }); - } - - - const boxContent = document.getElementById(`${boxId}-content`); - if (boxContent != null) { - // Add information about multipliers from research at the bottom of the popup - appendLineBreaks(boxContent, 2); - boxContent.appendChild(createElement("pre", { - display: "block", - innerText: `Multipliers from research:\n` + - ` * Advertising Multiplier: x${researchTree.getAdvertisingMultiplier()}\n` + - ` * Employee Charisma Multiplier: x${researchTree.getEmployeeChaMultiplier()}\n` + - ` * Employee Creativity Multiplier: x${researchTree.getEmployeeCreMultiplier()}\n` + - ` * Employee Efficiency Multiplier: x${researchTree.getEmployeeEffMultiplier()}\n` + - ` * Employee Intelligence Multiplier: x${researchTree.getEmployeeIntMultiplier()}\n` + - ` * Production Multiplier: x${researchTree.getProductionMultiplier()}\n` + - ` * Sales Multiplier: x${researchTree.getSalesMultiplier()}\n` + - ` * Scientific Research Multiplier: x${researchTree.getScientificResearchMultiplier()}\n` + - ` * Storage Multiplier: x${researchTree.getStorageMultiplier()}`, - })); - - // Close button - boxContent.appendChild(createPopupCloseButton(researchTreeBox, { - class: "std-button", - display: "block", - innerText: "Close", - })); - } - - researchTreeBoxOpened = true; -} - -Industry.prototype.toJSON = function() { - return Generic_toJSON("Industry", this); -} - -Industry.fromJSON = function(value) { - return Generic_fromJSON(Industry, value.data); -} - -Reviver.constructors.Industry = Industry; - -function Employee(params={}) { - if (!(this instanceof Employee)) { - return new Employee(params); - } - this.name = params.name ? params.name : "Bobby"; - - //Morale, happiness, and energy are 0-100 - this.mor = params.morale ? params.morale : getRandomInt(50, 100); - this.hap = params.happiness ? params.happiness : getRandomInt(50, 100); - this.ene = params.energy ? params.energy : getRandomInt(50, 100); - - this.age = params.age ? params.age : getRandomInt(20, 50); - this.int = params.intelligence ? params.intelligence : getRandomInt(10, 50); - this.cha = params.charisma ? params.charisma : getRandomInt(10, 50); - this.exp = params.experience ? params.experience : getRandomInt(10, 50); - this.cre = params.creativity ? params.creativity : getRandomInt(10, 50); - this.eff = params.efficiency ? params.efficiency : getRandomInt(10, 50); - this.sal = params.salary ? params.salary : getRandomInt(0.1, 5); - this.pro = 0; //Productivity, This is calculated - - this.cyclesUntilRaise = CyclesPerEmployeeRaise; - - this.loc = params.loc ? params.loc : ""; - this.pos = EmployeePositions.Unassigned; -} - -//Returns the amount the employee needs to be paid -Employee.prototype.process = function(marketCycles=1, office) { - var gain = 0.001 * marketCycles, - det = gain * Math.random(); - this.age += gain; - this.exp += gain; - if (this.age > 150) { - this.int -= det; - this.eff -= det; - this.cha -= det; - } - - // Employee salaries slowly go up over time - this.cyclesUntilRaise -= marketCycles; - if (this.cyclesUntilRaise <= 0) { - this.salary += EmployeeRaiseAmount; - this.cyclesUntilRaise += CyclesPerEmployeeRaise; - } - - //Training - var trainingEff = gain * Math.random(); - if (this.pos === EmployeePositions.Training) { - //To increase creativity and intelligence special upgrades are needed - this.cha += trainingEff; - this.exp += trainingEff; - this.eff += trainingEff; - } - - //Weight based on how full office is - //Too many employees = more likely to decrease energy and happiness - var officeCapacityWeight = 0.5 * (office.employees.length / office.size - 0.5); - if (Math.random() < 0.5 - officeCapacityWeight) { - this.ene += det; - this.hap += det; - } else { - this.ene -= det; - this.hap -= det; - } - if (this.ene < office.minEne) {this.ene = office.minEne;} - if (this.hap < office.minHap) {this.hap = office.minHap;} - var salary = this.sal * marketCycles * SecsPerMarketCycle; - return salary; -} - -Employee.prototype.calculateProductivity = function(corporation, industry) { - var effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), - effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), - effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), - effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier(); - var prodBase = this.mor * this.hap * this.ene * 1e-6, prodMult; - switch(this.pos) { - //Calculate productivity based on position. This is multipled by prodBase - //to get final value - case EmployeePositions.Operations: - prodMult = (0.6 * effInt) + (0.1 * effCha) + (this.exp) + - (0.5 * effCre) + (effEff); - break; - case EmployeePositions.Engineer: - prodMult = (effInt) + (0.1 * effCha) + (1.5 * this.exp) + - (effEff); - break; - case EmployeePositions.Business: - prodMult = (0.4 * effInt) + (effCha) + (0.5 * this.exp); - break; - case EmployeePositions.Management: - prodMult = (2 * effCha) + (this.exp) + (0.2 * effCre) + - (0.7 * effEff); - break; - case EmployeePositions.RandD: - prodMult = (1.5 * effInt) + (0.8 * this.exp) + (effCre) + - (0.5 * effEff); - break; - case EmployeePositions.Unassigned: - case EmployeePositions.Training: - prodMult = 0; - break; - default: - console.log("ERROR: Invalid employee position: " + this.pos); - break; - } - return prodBase * prodMult; -} - -//Process benefits from having an office party thrown -Employee.prototype.throwParty = function(money) { - var mult = 1 + (money / 10e6); - this.mor *= mult; - this.mor = Math.min(100, this.mor); - this.hap *= mult; - this.hap = Math.min(100, this.hap); - return mult; -} - -//'panel' is the DOM element on which to create the UI -Employee.prototype.createUI = function(panel, corporation, industry) { - var effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), - effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), - effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), - effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier(); - panel.style.color = "white"; - panel.appendChild(createElement("p", { - id:"cmpy-mgmt-employee-" + this.name + "-panel-text", - innerHTML:"Morale: " + formatNumber(this.mor, 3) + "
" + - "Happiness: " + formatNumber(this.hap, 3) + "
" + - "Energy: " + formatNumber(this.ene, 3) + "
" + - "Age: " + formatNumber(this.age, 3) + "
" + - "Intelligence: " + formatNumber(effInt, 3) + "
" + - "Charisma: " + formatNumber(effCha, 3) + "
" + - "Experience: " + formatNumber(this.exp, 3) + "
" + - "Creativity: " + formatNumber(effCre, 3) + "
" + - "Efficiency: " + formatNumber(effEff, 3) + "
" + - "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s
", - })); - - //Selector for employee position - var selector = createElement("select", {}); - for (var key in EmployeePositions) { - if (EmployeePositions.hasOwnProperty(key)) { - selector.add(createElement("option", { - text: EmployeePositions[key], - value: EmployeePositions[key], - })); - } - } - - selector.addEventListener("change", ()=>{ - this.pos = selector.options[selector.selectedIndex].value; - }); - - //Set initial value of selector - for (var i = 0; i < selector.length; ++i) { - if (selector.options[i].value === this.pos) { - selector.selectedIndex = i; - break; - } - } - panel.appendChild(selector); -} - -Employee.prototype.updateUI = function(panel, corporation, industry) { - var effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), - effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), - effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), - effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier(); - if (panel == null) { - console.log("ERROR: Employee.updateUI() called with null panel"); - return; - } - var text = document.getElementById("cmpy-mgmt-employee-" + this.name + "-panel-text"); - if (text == null) { - return this.createUI(panel); - } - text.innerHTML = "Morale: " + formatNumber(this.mor, 3) + "
" + - "Happiness: " + formatNumber(this.hap, 3) + "
" + - "Energy: " + formatNumber(this.ene, 3) + "
" + - "Age: " + formatNumber(this.age, 3) + "
" + - "Intelligence: " + formatNumber(effInt, 3) + "
" + - "Charisma: " + formatNumber(effCha, 3) + "
" + - "Experience: " + formatNumber(this.exp, 3) + "
" + - "Creativity: " + formatNumber(effCre, 3) + "
" + - "Efficiency: " + formatNumber(effEff, 3) + "
" + - "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s
"; -} - -Employee.prototype.toJSON = function() { - return Generic_toJSON("Employee", this); -} - -Employee.fromJSON = function(value) { - return Generic_fromJSON(Employee, value.data); -} - -Reviver.constructors.Employee = Employee; - -var OfficeSpaceTiers = { - Basic: "Basic", - Enhanced: "Enhanced", - Luxurious: "Luxurious", - Extravagant: "Extravagant" -} - -function OfficeSpace(params={}) { - this.loc = params.loc ? params.loc : ""; - this.cost = params.cost ? params.cost : 1; - this.size = params.size ? params.size : 1; - this.comf = params.comfort ? params.comfort : 1; - this.beau = params.beauty ? params.beauty : 1; - this.tier = OfficeSpaceTiers.Basic; - - // Min/max energy of employees - this.minEne = 0; - this.maxEne = 100; - - // Min/max Happiness of office - this.minHap = 0; - this.maxHap = 100; - - // Maximum Morale of office - this.maxMor = 100; - - this.employees = []; - this.employeeProd = { - [EmployeePositions.Operations]: 0, - [EmployeePositions.Engineer]: 0, - [EmployeePositions.Business]: 0, - [EmployeePositions.Management]: 0, - [EmployeePositions.RandD]: 0, - total: 0, - }; -} - -OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) { - var corporation = parentRefs.corporation, industry = parentRefs.industry; - - // Process Office properties - this.maxEne = 100; - this.maxHap = 100; - this.maxMor = 100; - if (industry.hasResearch("Go-Juice")) { - this.maxEne += 10; - } - if (industry.hasResearch("JoyWire")) { - this.maxHap += 10; - } - if (industry.hasResearch("Sti.mu")) { - this.maxMor += 10; - } - - // Calculate changes in Morale/Happiness/Energy for Employees - var perfMult=1; //Multiplier for employee morale/happiness/energy based on company performance - if (industry.funds < 0 && industry.lastCycleRevenue < 0) { - perfMult = Math.pow(0.99, marketCycles); - } else if (industry.funds > 0 && industry.lastCycleRevenue > 0) { - perfMult = Math.pow(1.01, marketCycles); - } - - const hasAutobrew = industry.hasResearch("AutoBrew"); - const hasAutoparty = industry.hasResearch("AutoPartyManager"); - - var salaryPaid = 0; - for (let i = 0; i < this.employees.length; ++i) { - const emp = this.employees[i]; - if (hasAutoparty) { - emp.mor = this.maxMor; - emp.hap = this.maxHap; - } else { - emp.mor *= perfMult; - emp.hap *= perfMult; - emp.mor = Math.min(emp.mor, this.maxMor); - emp.hap = Math.min(emp.hap, this.maxHap); - } - - if (hasAutobrew) { - emp.ene = this.maxEne; - } else { - emp.ene *= perfMult; - emp.ene = Math.min(emp.ene, this.maxEne); - } - - const salary = emp.process(marketCycles, this); - salaryPaid += salary; - } - - this.calculateEmployeeProductivity(marketCycles, parentRefs); - return salaryPaid; -} - -OfficeSpace.prototype.calculateEmployeeProductivity = function(marketCycles=1, parentRefs) { - var company = parentRefs.corporation, industry = parentRefs.industry; - - //Reset - for (var name in this.employeeProd) { - if (this.employeeProd.hasOwnProperty(name)) { - this.employeeProd[name] = 0; - } - } - - var total = 0; - for (var i = 0; i < this.employees.length; ++i) { - var employee = this.employees[i]; - var prod = employee.calculateProductivity(company, industry); - this.employeeProd[employee.pos] += prod; - total += prod; - } - this.employeeProd["total"] = total; -} - -//Takes care of UI as well -OfficeSpace.prototype.findEmployees = function(parentRefs) { - var company = parentRefs.corporation, division = parentRefs.industry; - if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} - - //Generate three random employees (meh, decent, amazing) - var mult1 = getRandomInt(25, 50)/100, - mult2 = getRandomInt(51, 75)/100, - mult3 = getRandomInt(76, 100)/100; - var int = getRandomInt(50, 100), - cha = getRandomInt(50, 100), - exp = getRandomInt(50, 100), - cre = getRandomInt(50, 100), - eff = getRandomInt(50, 100), - sal = 2.2 * (int + cha + exp + cre + eff); - - var emp1 = new Employee({ - intelligence: int * mult1, - charisma: cha * mult1, - experience: exp * mult1, - creativity: cre * mult1, - efficiency: eff * mult1, - salary: sal * mult1, - }); - - var emp2 = new Employee({ - intelligence: int * mult2, - charisma: cha * mult2, - experience: exp * mult2, - creativity: cre * mult2, - efficiency: eff * mult2, - salary: sal * mult2, - }); - - var emp3 = new Employee({ - intelligence: int * mult3, - charisma: cha * mult3, - experience: exp * mult3, - creativity: cre * mult3, - efficiency: eff * mult3, - salary: sal * mult3, - }); - - var text = createElement("h1", { - innerHTML: "Select one of the following candidates for hire:", - }); - - var createEmpDiv = function(employee, office) { - var div = createElement("div", { - class:"cmpy-mgmt-find-employee-option", - innerHTML: "Intelligence: " + formatNumber(employee.int, 1) + "
" + - "Charisma: " + formatNumber(employee.cha, 1) + "
" + - "Experience: " + formatNumber(employee.exp, 1) + "
" + - "Creativity: " + formatNumber(employee.cre, 1) + "
" + - "Efficiency: " + formatNumber(employee.eff, 1) + "
" + - "Salary: " + numeralWrapper.format(employee.sal, '$0.000a') + " \ s
", - clickListener:()=>{ - office.hireEmployee(employee, parentRefs); - removeElementById("cmpy-mgmt-hire-employee-popup"); - return false; - } - }); - return div; - }; - - var cancelBtn = createElement("a", { - class:"a-link-button", - innerText:"Cancel", - float:"right", - clickListener:()=>{ - removeElementById("cmpy-mgmt-hire-employee-popup"); - return false; - } - }); - - var elems = [text, - createEmpDiv(emp1, this), - createEmpDiv(emp2, this), - createEmpDiv(emp3, this), - cancelBtn]; - - createPopup("cmpy-mgmt-hire-employee-popup", elems); -} - -OfficeSpace.prototype.hireEmployee = function(employee, parentRefs) { - var company = parentRefs.corporation, division = parentRefs.industry; - var yesBtn = yesNoTxtInpBoxGetYesButton(), - noBtn = yesNoTxtInpBoxGetNoButton(); - yesBtn.innerHTML = "Hire"; - noBtn.innerHTML = "Cancel"; - yesBtn.addEventListener("click", ()=>{ - var name = yesNoTxtInpBoxGetInput(); - for (var i = 0; i < this.employees.length; ++i) { - if (this.employees[i].name === name) { - dialogBoxCreate("You already have an employee with this nickname! Please give every employee a unique nickname."); - return false; - } - } - employee.name = name; - this.employees.push(employee); - company.displayDivisionContent(division, currentCityUi); - return yesNoTxtInpBoxClose(); - }); - noBtn.addEventListener("click", ()=>{ - return yesNoTxtInpBoxClose(); - }); - yesNoTxtInpBoxCreate("Give your employee a nickname!"); -} - -OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) { - var company = parentRefs.corporation, division = parentRefs.industry; - if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} - - //Generate three random employees (meh, decent, amazing) - var mult = getRandomInt(76, 100)/100; - var int = getRandomInt(50, 100), - cha = getRandomInt(50, 100), - exp = getRandomInt(50, 100), - cre = getRandomInt(50, 100), - eff = getRandomInt(50, 100), - sal = 2.2 * (int + cha + exp + cre + eff); - - var emp = new Employee({ - intelligence: int * mult, - charisma: cha * mult, - experience: exp * mult, - creativity: cre * mult, - efficiency: eff * mult, - salary: sal * mult, - }); - - var name = generateRandomString(7); - - for (var i = 0; i < this.employees.length; ++i) { - if (this.employees[i].name === name) { - return this.hireRandomEmployee(parentRefs); - } - } - emp.name = name; - this.employees.push(emp); - company.displayDivisionContent(division, currentCityUi); -} - -//Finds the first unassigned employee and assigns its to the specified job -OfficeSpace.prototype.assignEmployeeToJob = function(job) { - for (var i = 0; i < this.employees.length; ++i) { - if (this.employees[i].pos === EmployeePositions.Unassigned) { - this.employees[i].pos = job; - return true; - } - } - return false; -} - -//Finds the first employee with the given job and unassigns it -OfficeSpace.prototype.unassignEmployeeFromJob = function(job) { - for (var i = 0; i < this.employees.length; ++i) { - if (this.employees[i].pos === job) { - this.employees[i].pos = EmployeePositions.Unassigned; - return true; - } - } - return false; -} - -OfficeSpace.prototype.toJSON = function() { - return Generic_toJSON("OfficeSpace", this); -} - -OfficeSpace.fromJSON = function(value) { - return Generic_fromJSON(OfficeSpace, value.data); -} - -Reviver.constructors.OfficeSpace = OfficeSpace; - -function Warehouse(params={}) { - this.loc = params.loc ? params.loc : ""; - this.size = params.size ? params.size : 0; - this.level = 0; - this.sizeUsed = 0; - this.smartSupplyEnabled = false; //Whether or not smart supply is enabled - this.breakdown = ""; - - //Stores the amount of product to be produced. Used for Smart Supply unlock. - //The production tracked by smart supply is always based on the previous cycle, - //so it will always trail the "true" production by 1 cycle - this.smartSupplyStore = 0; - - this.materials = { - Water: new Material({name: "Water"}), - Energy: new Material({name: "Energy"}), - Food: new Material({name: "Food"}), - Plants: new Material({name: "Plants"}), - Metal: new Material({name: "Metal"}), - Hardware: new Material({name: "Hardware"}), - Chemicals: new Material({name: "Chemicals"}), - Drugs: new Material({name: "Drugs"}), - Robots: new Material({name: "Robots"}), - AICores: new Material({name: "AI Cores"}), - RealEstate: new Material({name: "Real Estate"}) - } -} - -Warehouse.prototype.updateMaterialSizeUsed = function() { - this.sizeUsed = 0; - this.breakdown = ""; - for (var matName in this.materials) { - if (this.materials.hasOwnProperty(matName)) { - var mat = this.materials[matName]; - if (MaterialSizes.hasOwnProperty(matName)) { - this.sizeUsed += (mat.qty * MaterialSizes[matName]); - if (mat.qty > 0) { - this.breakdown += (matName + ": " + formatNumber(mat.qty * MaterialSizes[matName], 0) + "
"); - } - } - } - } - if (this.sizeUsed > this.size) { - console.log("ERROR: Warehouse size used greater than capacity, something went wrong"); - } -} - -Warehouse.prototype.updateSize = function(corporation, industry) { - //Backwards compatibility - if (this.level == null || this.level === 0) { - this.level = Math.round(this.size / 100); - } - - this.size = (this.level * 100) - * corporation.getStorageMultiplier() - * industry.getStorageMultiplier(); -} - -Warehouse.prototype.createUI = function(parentRefs) { - if (parentRefs.company == null || parentRefs.industry == null) { - console.log("ERROR: Warehouse.createUI called without parentRefs.company or parentRefs.industry"); - return; - } - var company = parentRefs.company, industry = parentRefs.industry; - removeChildrenFromElement(industryWarehousePanel); - industryWarehouseStorageText = createElement("p", { - display:"inline-block", class:"tooltip", - color: this.sizeUsed >= this.size ? "red" : "white", - }); - industryWarehousePanel.appendChild(industryWarehouseStorageText); - - //Upgrade warehouse size button - var upgradeCost = WarehouseUpgradeBaseCost * Math.pow(1.07, this.level+1); - industryWarehouseUpgradeSizeButton = createElement("a", { - innerText:"Upgrade Warehouse Size - " + numeralWrapper.format(upgradeCost, '$0.000a'), - display:"inline-block", - class: company.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button", - clickListener:()=>{ - //Backwards compatibility - if (this.level == null || this.level === 0) { - this.level = Math.round(this.size / 100); - } - - ++this.level; - this.updateSize(company, industry); - company.funds = company.funds.minus(upgradeCost); - this.createUI(parentRefs); - return; - } - }); - industryWarehousePanel.appendChild(industryWarehouseUpgradeSizeButton); - - //Material requirement text - var reqText = "This Industry uses [" + Object.keys(industry.reqMats).join(", ") + - "] in order to "; - if (industry.prodMats.length > 0) { - reqText += "produce [" + industry.prodMats.join(", ") + "] "; - if (industry.makesProducts) { - reqText += " and " + industry.getProductDescriptionText(); - } - } else if (industry.makesProducts) { - reqText += (industry.getProductDescriptionText() + "."); - } - - //Material ratio text for tooltip - var reqRatioText = ". The exact requirements for production are:
"; - for (var matName in industry.reqMats) { - if (industry.reqMats.hasOwnProperty(matName)) { - reqRatioText += ([" *", industry.reqMats[matName], matName].join(" ") + "
"); - } - } - reqRatioText += "in order to create "; - if (industry.prodMats.length > 0) { - reqRatioText += "one of each produced Material (" + industry.prodMats.join(", ") + ") "; - if (industry.makesProducts) { - reqRatioText += "or to create one of its Products"; - } - } else if (industry.makesProducts) { - reqRatioText += "one of its Products"; - } - - reqText += reqRatioText; - - reqText += "

To get started with production, purchase your required " + - "materials or import them from another of your company's divisions.

"; - - industryWarehousePanel.appendChild(createElement("p", { innerHTML: reqText })); - - //Current state - industryWarehouseStateText = createElement("p"); - industryWarehousePanel.appendChild(industryWarehouseStateText); - - //Smart Supply Enable/Disable - if (company.unlockUpgrades[1]) { - if (this.smartSupplyEnabled == null) {this.smartSupplyEnabled = false;} - var smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox"; - industryWarehousePanel.appendChild(createElement("label", { - for:smartSupplyCheckboxId, innerText:"Enable Smart Supply", - color:"white" - })); - industrySmartSupplyCheckbox = createElement("input", { - type:"checkbox", id:smartSupplyCheckboxId, margin:"3px", - changeListener:()=>{ - this.smartSupplyEnabled = industrySmartSupplyCheckbox.checked; - } - }); - industrySmartSupplyCheckbox.checked = this.smartSupplyEnabled; - industryWarehousePanel.appendChild(industrySmartSupplyCheckbox); - } - - //Materials - industryWarehousePanel.appendChild(createElement("p", { - innerHTML: "
Materials:
", - })); - industryWarehouseMaterials = createElement("ul"); - industryWarehousePanel.appendChild(industryWarehouseMaterials); - - //Products - if (industry.makesProducts && Object.keys(industry.products).length > 0) { - industryWarehousePanel.appendChild(createElement("p", { - innerHTML: "
Products:
", - })); - industryWarehouseProducts = createElement("ul"); - industryWarehousePanel.appendChild(industryWarehouseProducts); - } - - this.updateUI(parentRefs); -} - -Warehouse.prototype.updateUI = function(parentRefs) { - if (parentRefs.company == null || parentRefs.industry == null) { - console.log("ERROR: Warehouse.updateUI called without parentRefs.company or parentRefs.industry"); - return; - } - var company = parentRefs.company, industry = parentRefs.industry; - - //Storage text - var storageText = "Storage: " + - (this.sizedUsed >= this.size ? formatNumber(this.sizeUsed, 3) : formatNumber(this.sizeUsed, 3)) + - "/" + formatNumber(this.size, 3); - if (this.breakdown != null && this.breakdown != "") { - storageText += ("" + - this.breakdown + ""); - } - industryWarehouseStorageText.innerHTML = storageText; - - //Upgrade warehouse size button - var upgradeCost = WarehouseUpgradeBaseCost * Math.pow(1.07, this.level+1); - if (company.funds.lt(upgradeCost)) { - industryWarehouseUpgradeSizeButton.className = "a-link-button-inactive"; - } else { - industryWarehouseUpgradeSizeButton.className = "a-link-button"; - } - - //Current state - var stateText = "Current state: "; - switch(industry.state) { - case "START": - stateText += "Preparing..."; - break; - case "PURCHASE": - stateText += "Purchasing materials..."; - break; - case "PRODUCTION": - stateText += "Producing materials and/or products..."; - break; - case "SALE": - stateText += "Selling materials and/or products..."; - break; - case "EXPORT": - stateText += "Exporting materials and/or products..."; - break; - default: - console.log("ERROR: Invalid state: " + industry.state); - break; - } - industryWarehouseStateText.innerText = stateText; - - //Materials - removeChildrenFromElement(industryWarehouseMaterials); - for (var matName in this.materials) { - if (this.materials.hasOwnProperty(matName) && this.materials[matName] instanceof Material) { - if (Object.keys(industry.reqMats).includes(matName) || industry.prodMats.includes(matName) || - matName === "Hardware" || matName === "Robots" || matName === "AICores" || - matName === "RealEstate") { - industryWarehouseMaterials.appendChild(this.createMaterialUI(this.materials[matName], matName, parentRefs)); - } - } - } - - //Products - removeChildrenFromElement(industryWarehouseProducts); - if (industry.makesProducts && Object.keys(industry.products).length > 0) { - for (var productName in industry.products) { - if (industry.products.hasOwnProperty(productName) && industry.products[productName] instanceof Product) { - industryWarehouseProducts.appendChild(this.createProductUI(industry.products[productName], parentRefs)); - } - } - } -} - -Warehouse.prototype.createMaterialUI = function(mat, matName, parentRefs) { - if (parentRefs.company == null || parentRefs.industry == null) { - console.log("ERROR: Warehouse.createMaterialUI called without industry or company parent refs"); - return; - } - var company = parentRefs.company, industry = parentRefs.industry; - var purchasePopupId = "cmpy-mgmt-material-purchase-popup", - sellPopupid = "cmpy-mgmt-material-sell-popup"; - var div = createElement("div", { - class:"cmpy-mgmt-warehouse-material-div", - }); - - var totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp; - - //If Market Research upgrades are unlocked, add competition and demand info - var cmpAndDmdText = ""; - if (company.unlockUpgrades[2] === 1) { - cmpAndDmdText += "
Demand: " + formatNumber(mat.dmd, 3); - } - if (company.unlockUpgrades[3] === 1) { - cmpAndDmdText += "
Competition: " + formatNumber(mat.cmp, 3); - } - var innerTxt = "

" + mat.name + ": " + formatNumber(mat.qty, 3) + - "(" + formatNumber(totalGain, 3) + "/s)" + - "Buy: " + formatNumber(mat.buy, 3) + - "/s
Prod: " + formatNumber(mat.prd, 3) + "/s
Sell: " + formatNumber(mat.sll, 3) + - "/s
Export: " + formatNumber(mat.totalExp, 3) + "/s
Import: " + - formatNumber(mat.imp, 3) + "/s" + cmpAndDmdText + "


" + - "

MP: $" + formatNumber(mat.bCost, 2) + - "Market Price: The price you would pay if " + - "you were to buy this material on the market


" + - "

Quality: " + formatNumber(mat.qlt, 2) + - "The quality of your material. Higher quality " + - "will lead to more sales

"; - - div.appendChild(createElement("p", { - innerHTML: innerTxt, - id: "cmpy-mgmt-warehouse-" + matName + "-text", display:"inline-block", - })); - - var buttonPanel = createElement("div", { - display:"inline-block", - }); - div.appendChild(buttonPanel); - - //Button to set purchase amount - var tutorial = industry.newInd && Object.keys(industry.reqMats).includes(mat.name) && - mat.buy === 0 && mat.imp === 0; - var buyButtonParams = { - innerText: "Buy (" + formatNumber(mat.buy, 3) + ")", display:"inline-block", - class: tutorial ? "a-link-button flashing-button" : "a-link-button", - clickListener:()=>{ - var txt = createElement("p", { - innerHTML: "Enter the amount of " + mat.name + " you would like " + - "to purchase per second. This material's cost changes constantly" - }); - var confirmBtn; - var input = createElement("input", { - margin: "5px", - placeholder: "Purchase amount", - type: "number", - value: mat.buy ? mat.buy : null, - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - confirmBtn = createElement("button", { - innerText:"Confirm", class:"std-button", - clickListener:()=>{ - if (isNaN(input.value)) { - dialogBoxCreate("Invalid amount"); - } else { - mat.buy = parseFloat(input.value); - if (isNaN(mat.buy)) {mat.buy = 0;} - removeElementById(purchasePopupId); - this.createUI(parentRefs); - return false; - } - } - }); - var clearButton = createElement("button", { - innerText:"Clear Purchase", class:"std-button", - clickListener:()=>{ - mat.buy = 0; - removeElementById(purchasePopupId); - this.createUI(parentRefs); - return false; - } - }); - const cancelBtn = createPopupCloseButton(purchasePopupId, { - class: "std-button", - innerText: "Cancel", - }); - - createPopup(purchasePopupId, [txt, input, confirmBtn, clearButton, cancelBtn]); - input.focus(); - } - }; - if (tutorial) { - buyButtonParams.tooltip = "Purchase your required materials to get production started!"; - } - buttonPanel.appendChild(createElement("a", buyButtonParams)); - - //Button to manage exports - if (company.unlockUpgrades[0] === 1) { //Export unlock upgrade - function createExportPopup() { - var popupId = "cmpy-mgmt-export-popup"; - var exportTxt = createElement("p", { - innerText:"Select the industry and city to export this material to, as well as " + - "how much of this material to export per second. You can set the export " + - "amount to 'MAX' to export all of the materials in this warehouse." - }); - - //Select industry and city to export to - var citySelector = createElement("select", {class: "dropdown"}); - var industrySelector = createElement("select", { - class: "dropdown", - changeListener:()=>{ - var industryName = industrySelector.options[industrySelector.selectedIndex].value; - for (var foo = 0; foo < company.divisions.length; ++foo) { - if (company.divisions[foo].name == industryName) { - clearSelector(citySelector); - var selectedIndustry = company.divisions[foo]; - for (var cityName in company.divisions[foo].warehouses) { - if (company.divisions[foo].warehouses[cityName] instanceof Warehouse) { - citySelector.add(createElement("option", { - value:cityName, text:cityName, - })); - } - } - return; - } - } - } - }); - - for (var i = 0; i < company.divisions.length; ++i) { - industrySelector.add(createElement("option", { - text:company.divisions[i].name, value:company.divisions[i].name, - })); //End create element option - } //End for - - var currIndustry = industrySelector.options[industrySelector.selectedIndex].value; - for (var i = 0; i < company.divisions.length; ++i) { - if (company.divisions[i].name == currIndustry) { - for (var cityName in company.divisions[i].warehouses) { - if (company.divisions[i].warehouses.hasOwnProperty(cityName) && - company.divisions[i].warehouses[cityName] instanceof Warehouse) { - citySelector.add(createElement("option", { - value:cityName, text:cityName, - })); - } - } - break; - } - } - - //Select amount to export - var exportAmount = createElement("input", { - class: "text-input", - placeholder:"Export amount / s" - }); - - var exportBtn = createElement("a", { - class:"a-link-button", display:"inline-block", innerText:"Export", - clickListener:()=>{ - var industryName = industrySelector.options[industrySelector.selectedIndex].text, - cityName = citySelector.options[citySelector.selectedIndex].text, - amt = exportAmount.value; - //Sanitize amt - var sanitizedAmt = amt.replace(/\s+/g, ''); - sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, ''); - var temp = sanitizedAmt.replace(/MAX/g, 1); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid expression entered for export amount: " + e); - return false; - } - - if (temp == null || isNaN(temp)) { - dialogBoxCreate("Invalid amount entered for export"); - return; - } - var exportObj = {ind:industryName, city:cityName, amt:sanitizedAmt}; - mat.exp.push(exportObj); - removeElementById(popupId); - return false; - } - }); - - var cancelBtn = createElement("a", { - class:"a-link-button", display:"inline-block", innerText:"Cancel", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - - var currExportsText = createElement("p", { - innerText:"Below is a list of all current exports of this material from this warehouse. " + - "Clicking on one of the exports below will REMOVE that export." - }); - var currExports = []; - for (var i = 0; i < mat.exp.length; ++i) { - (function(i, mat, currExports){ - currExports.push(createElement("div", { - class:"cmpy-mgmt-existing-export", - innerHTML: "Industry: " + mat.exp[i].ind + "
" + - "City: " + mat.exp[i].city + "
" + - "Amount/s: " + mat.exp[i].amt, - clickListener:()=>{ - mat.exp.splice(i, 1); //Remove export object - removeElementById(popupId); - createExportPopup(); - } - })); - })(i, mat, currExports); - } - createPopup(popupId, [exportTxt, industrySelector, citySelector, exportAmount, - exportBtn, cancelBtn, currExportsText].concat(currExports)); - } - buttonPanel.appendChild(createElement("a", { - innerText:"Export", display:"inline-block", class:"a-link-button", - clickListener:()=>{createExportPopup();} - })); - } - - buttonPanel.appendChild(createElement("br", {})); // Force line break - - //Button to set sell amount - var innerTextString; - if (mat.sllman[0]) { - innerTextString = (mat.sllman[1] === -1 ? "Sell (" + formatNumber(mat.sll, 3) + "/MAX)" : - "Sell (" + formatNumber(mat.sll, 3) + "/" + formatNumber(mat.sllman[1], 3) + ")"); - if (mat.sCost) { - if (isString(mat.sCost)) { - var sCost = mat.sCost.replace(/MP/g, mat.bCost); - innerTextString += " @ $" + formatNumber(eval(sCost), 2); - } else { - innerTextString += " @ $" + formatNumber(mat.sCost, 2); - } - } - } else { - innerTextString = "Sell (0.000/0.000)"; - } - - buttonPanel.appendChild(createElement("a", { - innerText: innerTextString, display:"inline-block", class:"a-link-button", - clickListener:()=>{ - var txt = createElement("p", { - innerHTML: "Enter the maximum amount of " + mat.name + " you would like " + - "to sell per second, as well as the price at which you would " + - "like to sell at.

" + - "If the sell amount is set to 0, then the material will not be sold. If the sell price " + - "if set to 0, then the material will be discarded

" + - "Setting the sell amount to 'MAX' will result in you always selling the " + - "maximum possible amount of the material.

" + - "When setting the sell amount, you can use the 'PROD' variable to designate a dynamically " + - "changing amount that depends on your production. For example, if you set the sell amount " + - "to 'PROD-5' then you will always sell 5 less of the material than you produce.

" + - "When setting the sell price, you can use the 'MP' variable to designate a dynamically " + - "changing price that depends on the market price. For example, if you set the sell price " + - "to 'MP+10' then it will always be sold at $10 above the market price.", - }); - var br = createElement("br", {}); - var confirmBtn; - var inputQty = createElement("input", { - type: "text", marginTop: "4px", - value: mat.sllman[1] ? mat.sllman[1] : null, placeholder: "Sell amount", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - var inputPx = createElement("input", { - type: "text", marginTop: "4px", - value: mat.sCost ? mat.sCost : null, placeholder: "Sell price", - onkeyup: (e) => { - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - confirmBtn = createElement("button", { - class: "std-button", - innerText: "Confirm", - clickListener: () => { - //Parse price - var cost = inputPx.value.replace(/\s+/g, ''); - cost = cost.replace(/[^-()\d/*+.MP]/g, ''); //Sanitize cost - var temp = cost.replace(/MP/g, mat.bCost); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid value or expression for sell price field: " + e); - return false; - } - - if (temp == null || isNaN(temp)) { - dialogBoxCreate("Invalid value or expression for sell price field"); - return false; - } - - if (cost.includes("MP")) { - mat.sCost = cost; //Dynamically evaluated - } else { - mat.sCost = temp; - } - - //Parse quantity - if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) { - var qty = inputQty.value.replace(/\s+/g, ''); - qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); - var temp = qty.replace(/MAX/g, 1); - temp = temp.replace(/PROD/g, 1); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid value or expression for sell price field: " + e); - return false; - } - - if (temp == null || isNaN(temp)) { - dialogBoxCreate("Invalid value or expression for sell price field"); - return false; - } - - mat.sllman[0] = true; - mat.sllman[1] = qty; //Use sanitized input - } else if (isNaN(inputQty.value)) { - dialogBoxCreate("Invalid value for sell quantity field! Must be numeric or 'MAX'"); - return false; - } else { - var qty = parseFloat(inputQty.value); - if (isNaN(qty)) {qty = 0;} - if (qty === 0) { - mat.sllman[0] = false; - mat.sllman[1] = 0; - } else { - mat.sllman[0] = true; - mat.sllman[1] = qty; - } - } - - this.createUI(parentRefs); - removeElementById(sellPopupid); - return false; - } - }); - const cancelBtn = createPopupCloseButton(sellPopupid, { - class: "std-button", - innerText: "Cancel", - }); - - createPopup(sellPopupid, [txt, br, inputQty, inputPx, confirmBtn, cancelBtn]); - inputQty.focus(); - } - })); - - // Button to use Market-TA research, if you have it - if (industry.hasResearch("Market-TA.I")) { - let marketTaClickListener = () => { - const popupId = "cmpy-mgmt-marketta-popup"; - const markupLimit = mat.getMarkupLimit(); - const ta1 = createElement("p", { - innerHTML: "Market-TA.I
" + - "The maximum sale price you can mark this up to is " + - numeralWrapper.format(mat.bCost + markupLimit, '$0.000a') + - ". This means that if you set the sale price higher than this, " + - "you will begin to experience a loss in number of sales", - }); - const closeBtn = createPopupCloseButton(popupId, { - class: "std-button", - display: "block", - }); - - if (industry.hasResearch("Market-TA.II")) { - let updateTa2Text; - const ta2Text = createElement("p"); - const ta2Input = createElement("input", { - marginTop: "4px", - onkeyup: (e) => { - e.preventDefault(); - updateTa2Text(); - }, - type: "number", - value: mat.bCost, - }); - - // Function that updates the text in ta2Text element - updateTa2Text = function() { - const sCost = parseFloat(ta2Input.value); - let markup = 1; - if (sCost > mat.bCost) { - //Penalty if difference between sCost and bCost is greater than markup limit - if ((sCost - mat.bCost) > markupLimit) { - markup = Math.pow(markupLimit / (sCost - mat.bCost), 2); - } - } else if (sCost < mat.bCost) { - if (sCost <= 0) { - markup = 1e12; //Sell everything, essentially discard - } else { - //Lower prices than market increases sales - markup = mat.bCost / sCost; - } - } - ta2Text.innerHTML = `
Market-TA.II
` + - `If you sell at ${numeralWrapper.format(sCost, "$0.0001")}, ` + - `then you will sell ${formatNumber(markup, 5)}x as much compared ` + - `to if you sold at market price.`; - } - updateTa2Text(); - createPopup(popupId, [ta1, ta2Text, ta2Input, closeBtn]); - } else { - // Market-TA.I only - createPopup(popupId, [ta1, closeBtn]); - } - }; - - buttonPanel.appendChild(createElement("a", { - class: "a-link-button", - clickListener:() => { marketTaClickListener(); }, - display: "inline-block", - innerText: "Market-TA", - - })) - } - - return div; -} - -Warehouse.prototype.createProductUI = function(product, parentRefs) { - var company = parentRefs.company, industry = parentRefs.industry, - city = currentCityUi; - var div = createElement("div", { - class:"cmpy-mgmt-warehouse-product-div" - }); - - //Products being designed TODO - if (!product.fin) { - div.appendChild(createElement("p", { - innerHTML: "Designing " + product.name + "...
" + - formatNumber(product.prog, 2) + "% complete", - })); - return div; - } - - //Completed products - var cmpAndDmdText = ""; - if (company.unlockUpgrades[2] === 1) { - cmpAndDmdText += "
Demand: " + formatNumber(product.dmd, 3); - } - if (company.unlockUpgrades[3] === 1) { - cmpAndDmdText += "
Competition: " + formatNumber(product.cmp, 3); - } - - var totalGain = product.data[city][1] - product.data[city][2]; //Production - sale - div.appendChild(createElement("p", { - innerHTML: "

" + product.name + ": " + formatNumber(product.data[city][0], 3) + //Quantity - "(" + formatNumber(totalGain, 3) + "/s)" + - "Prod: " + formatNumber(product.data[city][1], 3) + "/s
" + - "Sell: " + formatNumber(product.data[city][2], 3) + "/s


" + - "

Rating: " + formatNumber(product.rat, 3) + - "Quality: " + formatNumber(product.qlt, 3) + "
" + - "Performance: " + formatNumber(product.per, 3) + "
" + - "Durability: " + formatNumber(product.dur, 3) + "
" + - "Reliability: " + formatNumber(product.rel, 3) + "
" + - "Aesthetics: " + formatNumber(product.aes, 3) + "
" + - "Features: " + formatNumber(product.fea, 3) + - cmpAndDmdText + "


" + - "

Est. Production Cost: " + numeralWrapper.format(product.pCost / ProductProductionCostRatio, "$0.000a") + - "An estimate of the material cost it takes to create this Product.


" + - "

Est. Market Price: " + numeralWrapper.format(product.pCost + product.rat / product.mku, "$0.000a") + - "An estimate of how much consumers are willing to pay for this product. " + - "Setting the sale price above this may result in less sales. Setting the sale price below this may result " + - "in more sales.

" - })); - var buttonPanel = createElement("div", { - display:"inline-block", - }); - div.appendChild(buttonPanel); - - //Sell button - var sellInnerTextString = (product.sllman[city][1] === -1 ? "Sell (" + formatNumber(product.data[city][2], 3) + "/MAX)" : - "Sell (" + formatNumber(product.data[city][2], 3) + "/" + formatNumber(product.sllman[city][1], 3) + ")"); - if (product.sCost) { - if (isString(product.sCost)) { - sellInnerTextString += (" @ " + product.sCost); - } else { - sellInnerTextString += (" @ " + numeralWrapper.format(product.sCost, "$0.000a")); - } - } - div.appendChild(createElement("a", { - innerText:sellInnerTextString, class:"a-link-button", display:"inline-block",margin:"6px", - clickListener:()=>{ - var popupId = "cmpy-mgmt-sell-product-popup"; - var txt = createElement("p", { - innerHTML:"Enter the maximum amount of " + product.name + " you would like " + - "to sell per second, as well as the price at which you would like to " + - "sell it at.

" + - "If the sell amount is set to 0, then the product will not be sold. If the " + - "sell price is set to 0, then the product will be discarded.

" + - "Setting the sell amount to 'MAX' will result in you always selling the " + - "maximum possible amount of the material.

" + - "When setting the sell amount, you can use the 'PROD' variable to designate a " + - "dynamically changing amount that depends on your production. For example, " + - "if you set the sell amount to 'PROD-1' then you will always sell 1 less of " + - "the material than you produce.

" + - "When setting the sell price, you can use the 'MP' variable to set a " + - "dynamically changing price that depends on the Product's estimated " + - "market price. For example, if you set it to 'MP*5' then it " + - "will always be sold at five times the estimated market price.", - }); - var confirmBtn; - var inputQty = createElement("input", { - type:"text", value:product.sllman[city][1] ? product.sllman[city][1] : null, placeholder: "Sell amount", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - var inputPx = createElement("input", { - type:"text", value: product.sCost ? product.sCost : null, placeholder: "Sell price", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - confirmBtn = createElement("a", { - class:"a-link-button", innerText:"Confirm", - clickListener:()=>{ - //Parse price - if (inputPx.value.includes("MP")) { - //Dynamically evaluated quantity. First test to make sure its valid - //Sanitize input, then replace dynamic variables with arbitrary numbers - var price = inputPx.value.replace(/\s+/g, ''); - price = price.replace(/[^-()\d/*+.MP]/g, ''); - var temp = price.replace(/MP/g, 1); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid value or expression for sell quantity field: " + e); - return false; - } - if (temp == null || isNaN(temp)) { - dialogBoxCreate("Invalid value or expression for sell quantity field."); - return false; - } - product.sCost = price; //Use sanitized price - } else { - var cost = parseFloat(inputPx.value); - if (isNaN(cost)) { - dialogBoxCreate("Invalid value for sell price field"); - return false; - } - product.sCost = cost; - } - - //Parse quantity - if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) { - //Dynamically evaluated quantity. First test to make sure its valid - var qty = inputQty.value.replace(/\s+/g, ''); - qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); - var temp = qty.replace(/MAX/g, 1); - temp = temp.replace(/PROD/g, 1); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid value or expression for sell price field: " + e); - return false; - } - - if (temp == null || isNaN(temp)) { - dialogBoxCreate("Invalid value or expression for sell price field"); - return false; - } - product.sllman[city][0] = true; - product.sllman[city][1] = qty; //Use sanitized input - } else if (isNaN(inputQty.value)) { - dialogBoxCreate("Invalid value for sell quantity field! Must be numeric"); - return false; - } else { - var qty = parseFloat(inputQty.value); - if (isNaN(qty)) {qty = 0;} - if (qty === 0) { - product.sllman[city][0] = false; - } else { - product.sllman[city][0] = true; - product.sllman[city][1] = qty; - } - } - this.createUI(parentRefs); - removeElementById(popupId); - return false; - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", innerText:"Cancel", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn]); - inputQty.focus(); - } - })); - div.appendChild(createElement("br",{})); //force line break - - //Limit production button - var limitProductionInnerText = "Limit Production"; - if (product.prdman[city][0]) { - limitProductionInnerText += " (" + formatNumber(product.prdman[city][1], 3) + ")"; - } - div.appendChild(createElement("a", { - class:"a-link-button", innerText:limitProductionInnerText,display:"inline-block", - clickListener:()=>{ - var popupId = "cmpy-mgmt-limit-product-production-popup"; - var txt = createElement("p", { - innerText:"Enter a limit to the amount of this product you would " + - "like to product per second. Leave the box empty to set no limit." - }); - var confirmBtn; - var input = createElement("input", { - type:"number", placeholder:"Limit", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - confirmBtn = createElement("a", { - class:"a-link-button", display:"inline-block", innerText:"Limit production", margin:'6px', - clickListener:()=>{ - if (input.value === "") { - product.prdman[city][0] = false; - removeElementById(popupId); - return false; - } - var qty = parseFloat(input.value); - if (isNaN(qty)) { - dialogBoxCreate("Invalid value entered"); - return false; - } - if (qty < 0) { - product.prdman[city][0] = false; - } else { - product.prdman[city][0] = true; - product.prdman[city][1] = qty; - } - removeElementById(popupId); - return false; - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", display:"inline-block", innerText:"Cancel", margin:"6px", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, input, confirmBtn, cancelBtn]); - } - })); - - //Discontinue button - div.appendChild(createElement("a", { - class:'a-link-button', display:"inline-block",innerText:"Discontinue", - clickListener:()=>{ - var popupId = "cmpy-mgmt-discontinue-product-popup"; - var txt = createElement("p", { - innerText:"Are you sure you want to do this? Discontinuing a product " + - "removes it completely and permanently. You will no longer " + - "produce this product and all of its existing stock will be " + - "removed and left unsold", - }); - var confirmBtn = createElement("a", { - class:"a-link-button",innerText:"Discontinue", - clickListener:()=>{ - industry.discontinueProduct(product, parentRefs); - removeElementById(popupId); - return false; - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", innerText:"Cancel", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, confirmBtn, cancelBtn]); - } - })); - return div; -} - -Warehouse.prototype.toJSON = function() { - return Generic_toJSON("Warehouse", this); -} - -Warehouse.fromJSON = function(value) { - return Generic_fromJSON(Warehouse, value.data); -} - -Reviver.constructors.Warehouse = Warehouse; - -function Corporation(params={}) { - this.name = params.name ? params.name : "The Corporation"; - - //A division/business sector is represented by the object: - this.divisions = []; - - //Financial stats - this.funds = new Decimal(150e9); - this.revenue = new Decimal(0); - this.expenses = new Decimal(0); - this.fundingRound = 0; - this.public = false; //Publicly traded - this.totalShares = INITIALSHARES; // Total existing shares - this.numShares = INITIALSHARES; // Total shares owned by player - this.shareSalesUntilPriceUpdate = SHARESPERPRICEUPDATE; - this.shareSaleCooldown = 0; // Game cycles until player can sell shares again - this.issueNewSharesCooldown = 0; // Game cycles until player can issue shares again - this.dividendPercentage = 0; - this.dividendTaxPercentage = 50; - this.issuedShares = 0; - this.sharePrice = 0; - this.storedCycles = 0; - - var numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length, - numUpgrades = Object.keys(CorporationUpgrades).length; - - this.unlockUpgrades = Array(numUnlockUpgrades).fill(0); - this.upgrades = Array(numUpgrades).fill(0); - this.upgradeMultipliers = Array(numUpgrades).fill(1); - - this.state = new CorporationState(); -} - -Corporation.prototype.getState = function() { - return this.state.getState(); -} - -Corporation.prototype.storeCycles = function(numCycles=1) { - this.storedCycles += numCycles; -} - -Corporation.prototype.process = function() { - var corp = this; - if (this.storedCycles >= CyclesPerIndustryStateCycle) { - const state = this.getState(); - const marketCycles = 1; - const gameCycles = (marketCycles * CyclesPerIndustryStateCycle); - this.storedCycles -= gameCycles; - - this.divisions.forEach(function(ind) { - ind.process(marketCycles, state, corp); - }); - - // Process cooldowns - if (this.shareSaleCooldown > 0) { - this.shareSaleCooldown -= gameCycles; - } - if (this.issueNewSharesCooldown > 0) { - this.issueNewSharesCooldown -= gameCycles; - } - - //At the start of a new cycle, calculate profits from previous cycle - if (state === "START") { - this.revenue = new Decimal(0); - this.expenses = new Decimal(0); - this.divisions.forEach((ind) => { - if (ind.lastCycleRevenue === -Infinity || ind.lastCycleRevenue === Infinity) { return; } - if (ind.lastCycleExpenses === -Infinity || ind.lastCycleExpenses === Infinity) { return; } - this.revenue = this.revenue.plus(ind.lastCycleRevenue); - this.expenses = this.expenses.plus(ind.lastCycleExpenses); - }); - var profit = this.revenue.minus(this.expenses); - const cycleProfit = profit.times(marketCycles * SecsPerMarketCycle); - if (isNaN(this.funds)) { - dialogBoxCreate("There was an error calculating your Corporations funds and they got reset to 0. " + - "This is a bug. Please report to game developer.

" + - "(Your funds have been set to $150b for the inconvenience)"); - this.funds = new Decimal(150e9); - } - - // Process dividends - if (this.dividendPercentage > 0 && cycleProfit > 0) { - // Validate input again, just to be safe - if (isNaN(this.dividendPercentage) || this.dividendPercentage < 0 || this.dividendPercentage > DividendMaxPercentage) { - console.error(`Invalid Corporation dividend percentage: ${this.dividendPercentage}`); - } else { - const totalDividends = (this.dividendPercentage / 100) * cycleProfit; - const retainedEarnings = cycleProfit - totalDividends; - const dividendsPerShare = totalDividends / this.totalShares; - const profit = this.numShares * dividendsPerShare * (1 - (this.dividendTaxPercentage / 100)); - Player.gainMoney(profit); - Player.recordMoneySource(profit, "corporation"); - this.funds = this.funds.plus(retainedEarnings); - } - } else { - this.funds = this.funds.plus(cycleProfit); - } - - this.updateSharePrice(); - } - - this.state.nextState(); - - if (routing.isOn(Page.Corporation)) {this.updateUIContent();} - } -} - -Corporation.prototype.determineValuation = function() { - var val, profit = (this.revenue.minus(this.expenses)).toNumber(); - if (this.public) { - // Account for dividends - if (this.dividendPercentage > 0) { - profit *= ((100 - this.dividendPercentage) / 100); - } - - val = this.funds.toNumber() + (profit * 85e3); - val *= (Math.pow(1.1, this.divisions.length)); - val = Math.max(val, 0); - } else { - val = 10e9 + Math.max(this.funds.toNumber(), 0) / 3; //Base valuation - if (profit > 0) { - val += (profit * 315e3); - val *= (Math.pow(1.1, this.divisions.length)); - } else { - val = 10e9 * Math.pow(1.1, this.divisions.length); - } - val -= (val % 1e6); //Round down to nearest millionth - } - return val * BitNodeMultipliers.CorporationValuation; -} - -Corporation.prototype.getInvestment = function() { - var val = this.determineValuation(), percShares; - let roundMultiplier = 4; - switch (this.fundingRound) { - case 0: //Seed - percShares = 0.10; - roundMultiplier = 5; - break; - case 1: //Series A - percShares = 0.35; - roundMultiplier = 4; - break; - case 2: //Series B - percShares = 0.25; - roundMultiplier = 4; - break; - case 3: //Series C - percShares = 0.20; - roundMultiplier = 3.5; - break; - case 4: - return; - } - var funding = val * percShares * roundMultiplier, - investShares = Math.floor(INITIALSHARES * percShares), - yesBtn = yesNoBoxGetYesButton(), - noBtn = yesNoBoxGetNoButton(); - yesBtn.innerHTML = "Accept"; - noBtn.innerHML = "Reject"; - yesBtn.addEventListener("click", ()=>{ - ++this.fundingRound; - this.funds = this.funds.plus(funding); - this.numShares -= investShares; - this.displayCorporationOverviewContent(); - return yesNoBoxClose(); - }); - noBtn.addEventListener("click", ()=>{ - return yesNoBoxClose(); - }); - yesNoBoxCreate("An investment firm has offered you " + numeralWrapper.format(funding, '$0.000a') + - " in funding in exchange for a " + numeralWrapper.format(percShares*100, "0.000a") + - "% stake in the company (" + numeralWrapper.format(investShares, '0.000a') + " shares).

" + - "Do you accept or reject this offer?

" + - "Hint: Investment firms will offer more money if your corporation is turning a profit"); -} - -Corporation.prototype.goPublic = function() { - var goPublicPopupId = "cmpy-mgmt-go-public-popup"; - var initialSharePrice = this.determineValuation() / (this.totalShares); - var txt = createElement("p", { - innerHTML: "Enter the number of shares you would like to issue " + - "for your IPO. These shares will be publicly sold " + - "and you will no longer own them. Your Corporation will receive " + - numeralWrapper.format(initialSharePrice, '$0.000a') + " per share " + - "(the IPO money will be deposited directly into your Corporation's funds).

" + - "You have a total of " + numeralWrapper.format(this.numShares, "0.000a") + " of shares that you can issue.", - }); - var yesBtn; - var input = createElement("input", { - type:"number", - placeholder: "Shares to issue", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {yesBtn.click();} - } - }); - var br = createElement("br", {}); - yesBtn = createElement("a", { - class:"a-link-button", - innerText:"Go Public", - clickListener:()=>{ - var numShares = Math.round(input.value); - var initialSharePrice = this.determineValuation() / (this.totalShares); - if (isNaN(numShares)) { - dialogBoxCreate("Invalid value for number of issued shares"); - return false; - } - if (numShares > this.numShares) { - dialogBoxCreate("Error: You don't have that many shares to issue!"); - return false; - } - this.public = true; - this.sharePrice = initialSharePrice; - this.issuedShares = numShares; - this.numShares -= numShares; - this.funds = this.funds.plus(numShares * initialSharePrice); - this.displayCorporationOverviewContent(); - removeElementById(goPublicPopupId); - dialogBoxCreate(`You took your ${this.name} public and earned ` + - `${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`); - return false; - } - }); - var noBtn = createElement("a", { - class:"a-link-button", - innerText:"Cancel", - clickListener:()=>{ - removeElementById(goPublicPopupId); - return false; - } - }); - createPopup(goPublicPopupId, [txt, br, input, yesBtn, noBtn]); -} - -Corporation.prototype.getTargetSharePrice = function() { - // Note: totalShares - numShares is not the same as issuedShares because - // issuedShares does not account for private investors - return this.determineValuation() / (2 * (this.totalShares - this.numShares) + 1); -} - -Corporation.prototype.updateSharePrice = function() { - const targetPrice = this.getTargetSharePrice(); - if (this.sharePrice <= targetPrice) { - this.sharePrice *= (1 + (Math.random() * 0.01)); - } else { - this.sharePrice *= (1 - (Math.random() * 0.01)); - } - if (this.sharePrice <= 0.01) {this.sharePrice = 0.01;} -} - -Corporation.prototype.immediatelyUpdateSharePrice = function() { - this.sharePrice = this.getTargetSharePrice(); -} - -// Calculates how much money will be made and what the resulting stock price -// will be when the player sells his/her shares -// @return - [Player profit, final stock price, end shareSalesUntilPriceUpdate property] -Corporation.prototype.calculateShareSale = function(numShares) { - let sharesTracker = numShares; - let sharesUntilUpdate = this.shareSalesUntilPriceUpdate; - let sharePrice = this.sharePrice; - let sharesSold = 0; - let profit = 0; - - const maxIterations = Math.ceil(numShares / SHARESPERPRICEUPDATE); - if (isNaN(maxIterations) || maxIterations > 10e6) { - console.error(`Something went wrong or unexpected when calculating share sale. Maxiterations calculated to be ${maxIterations}`); - return; - } - - for (let i = 0; i < maxIterations; ++i) { - if (sharesTracker < sharesUntilUpdate) { - profit += (sharePrice * sharesTracker); - sharesUntilUpdate -= sharesTracker; - break; - } else { - profit += (sharePrice * sharesUntilUpdate); - sharesUntilUpdate = SHARESPERPRICEUPDATE; - sharesTracker -= sharesUntilUpdate; - sharesSold += sharesUntilUpdate; - - // Calculate what new share price would be - sharePrice = this.determineValuation() / (2 * (this.totalShares + sharesSold - this.numShares)); - } - } - - return [profit, sharePrice, sharesUntilUpdate]; -} - -Corporation.prototype.convertCooldownToString = function(cd) { - // The cooldown value is based on game cycles. Convert to a simple string - const CyclesPerSecond = 1000 / CONSTANTS.MilliPerCycle; - const seconds = cd / 5; - - const SecondsPerMinute = 60; - const SecondsPerHour = 3600; - - if (seconds > SecondsPerHour) { - return `${Math.floor(seconds / SecondsPerHour)} hour(s)`; - } else if (seconds > SecondsPerMinute) { - return `${Math.floor(seconds / SecondsPerMinute)} minute(s)`; - } else { - return `${Math.floor(seconds)} second(s)`; - } -} - -//One time upgrades that unlock new features -Corporation.prototype.unlock = function(upgrade) { - const upgN = upgrade[0], price = upgrade[1]; - while (this.unlockUpgrades.length <= upgN) { - this.unlockUpgrades.push(0); - } - if (this.funds.lt(price)) { - dialogBoxCreate("You don't have enough funds to unlock this!"); - return; - } - this.unlockUpgrades[upgN] = 1; - this.funds = this.funds.minus(price); - - // Apply effects for one-time upgrades - if (upgN === 5) { - this.dividendTaxPercentage -= 5; - } else if (upgN === 6) { - this.dividendTaxPercentage -= 10; - } -} - -//Levelable upgrades -Corporation.prototype.upgrade = function(upgrade) { - var upgN = upgrade[0], basePrice = upgrade[1], priceMult = upgrade[2], - upgradeAmt = upgrade[3]; //Amount by which the upgrade multiplier gets increased (additive) - while (this.upgrades.length <= upgN) {this.upgrades.push(0);} - while (this.upgradeMultipliers.length <= upgN) {this.upgradeMultipliers.push(1);} - var totalCost = basePrice * Math.pow(priceMult, this.upgrades[upgN]); - if (this.funds.lt(totalCost)) { - dialogBoxCreate("You don't have enough funds to purchase this!"); - return; - } - ++this.upgrades[upgN]; - this.funds = this.funds.minus(totalCost); - - //Increase upgrade multiplier - this.upgradeMultipliers[upgN] = 1 + (this.upgrades[upgN] * upgradeAmt); - - //If storage size is being updated, update values in Warehouse objects - if (upgN === 1) { - for (var i = 0; i < this.divisions.length; ++i) { - var industry = this.divisions[i]; - for (var city in industry.warehouses) { - if (industry.warehouses.hasOwnProperty(city) && industry.warehouses[city] instanceof Warehouse) { - industry.warehouses[city].updateSize(this, industry); - } - } - } - } - - this.updateCorporationOverviewContent(); -} - -Corporation.prototype.getProductionMultiplier = function() { - var mult = this.upgradeMultipliers[0]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getStorageMultiplier = function() { - var mult = this.upgradeMultipliers[1]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getDreamSenseGain = function() { - var gain = this.upgradeMultipliers[2] - 1; - return gain <= 0 ? 0 : gain; -} - -Corporation.prototype.getAdvertisingMultiplier = function() { - var mult = this.upgradeMultipliers[3]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getEmployeeCreMultiplier = function() { - var mult = this.upgradeMultipliers[4]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getEmployeeChaMultiplier = function() { - var mult = this.upgradeMultipliers[5]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getEmployeeIntMultiplier = function() { - var mult = this.upgradeMultipliers[6]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getEmployeeEffMultiplier = function() { - var mult = this.upgradeMultipliers[7]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getSalesMultiplier = function() { - var mult = this.upgradeMultipliers[8]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getScientificResearchMultiplier = function() { - var mult = this.upgradeMultipliers[9]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -//Keep 'global' variables for DOM elements so we don't have to search -//through the DOM tree repeatedly when updating UI -var companyManagementDiv, companyManagementHeaderTabs, companyManagementPanel, - currentCityUi, - corporationUnlockUpgrades, corporationUpgrades, - - sellSharesButton, sellSharesButtonTooltip, - issueNewSharesButton, issueNewSharesButtonTooltip, - - //Industry Overview Panel - industryOverviewPanel, industryOverviewText, - - //Industry Employee Panel - industryEmployeePanel, industryEmployeeText, industryEmployeeHireButton, industryEmployeeAutohireButton, - industryEmployeeManagementUI, industryEmployeeInfo, industryIndividualEmployeeInfo, - industryOfficeUpgradeSizeButton, - - //Industry Warehouse Panel - industryWarehousePanel, industrySmartSupplyCheckbox, industryWarehouseStorageText, - industryWarehouseUpgradeSizeButton, industryWarehouseStateText, - industryWarehouseMaterials, industryWarehouseProducts, - - // Research Tree - researchTreeBoxOpened = false, - researchTreeBox, - - // Tabs - headerTabs, cityTabs; -Corporation.prototype.createUI = function() { - companyManagementDiv = createElement("div", { - id:"cmpy-mgmt-container", - position:"fixed", - class:"generic-menupage-container" - }); - companyManagementHeaderTabs = createElement("div", {id:"cmpy-mgmt-header-tabs"}); - companyManagementDiv.appendChild(companyManagementHeaderTabs); - - //Create division/industry tabs at the top - this.updateUIHeaderTabs(); - - //Create the 'panel' that will have the actual content in the UI - companyManagementPanel = createElement("div", {id:"cmpy-mgmt-panel"}); - companyManagementDiv.appendChild(companyManagementPanel); - document.getElementById("entire-game-container").appendChild(companyManagementDiv); - - this.displayCorporationOverviewContent(); -} - -Corporation.prototype.updateUIHeaderTabs = function() { - if (companyManagementHeaderTabs) { - removeChildrenFromElement(companyManagementHeaderTabs); - } else { - console.log("ERROR: Header tabs div has not yet been created when Corporation.updateUIHeaderTabs() is called"); - return; - } - - //Corporation overview tabs - var cmpyOverviewHdrTab = createElement("button", { - id:"cmpy-mgmt-company-tab", - class:"cmpy-mgmt-header-tab", - innerText:this.name, - checked:true, - clickListener:()=>{ - this.selectHeaderTab(cmpyOverviewHdrTab); - this.displayCorporationOverviewContent(); - return false; - } - }); - companyManagementHeaderTabs.appendChild(cmpyOverviewHdrTab); - - //Tabs for each division - for (var i = 0; i < this.divisions.length; ++i) { - this.createDivisionUIHeaderTab(this.divisions[i]); - } - - //Create a tab to expand into a new industry - companyManagementHeaderTabs.appendChild(createElement("button", { - id:'cmpy-mgmt-expand-industry-tab', - class:"cmpy-mgmt-header-tab", - innerText:"Expand into new Industry", - clickListener: ()=>{ - if (document.getElementById("cmpy-mgmt-expand-industry-popup") != null) {return;} - - var container = createElement("div", { - class:"popup-box-container", - id:"cmpy-mgmt-expand-industry-popup", - }); - var content = createElement("div", {class:"popup-box-content"}); - var txt = createElement("p", { - innerHTML: "Create a new division to expand into a new industry:", - }); - var selector = createElement("select", { - class:"dropdown" - }); - var industryDescription = createElement("p", {}); - var yesBtn; - var nameInput = createElement("input", { - type:"text", - id:"cmpy-mgmt-expand-industry-name-input", - class: "text-input", - display:"block", - maxLength: 30, - pattern:"[a-zA-Z0-9-_]", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {yesBtn.click();} - } - }); - var nameLabel = createElement("label", { - for:"cmpy-mgmt-expand-industry-name-input", - innerText:"Division name: " - }); - yesBtn = createElement("span", { - class:"popup-box-button", - innerText:"Create Division", - clickListener: ()=>{ - var ind = selector.options[selector.selectedIndex].value, - newDivisionName = nameInput.value; - - for (var i = 0; i < this.divisions.length; ++i) { - if (this.divisions[i].name === newDivisionName) { - dialogBoxCreate("This name is already in use!"); - return false; - } - } - if (this.funds.lt(IndustryStartingCosts[ind])) { - dialogBoxCreate("Not enough money to create a new division in this industry"); - } else if (newDivisionName === "") { - dialogBoxCreate("New division must have a name!"); - } else { - this.funds = this.funds.minus(IndustryStartingCosts[ind]); - var newInd = new Industry({ - name:newDivisionName, - type:ind, - }); - this.divisions.push(newInd); - this.updateUIHeaderTabs(); - this.selectHeaderTab(headerTabs[headerTabs.length-2]); - removeElementById("cmpy-mgmt-expand-industry-popup"); - this.displayDivisionContent(newInd, Locations.Sector12); - } - return false; - } - }); - - const noBtn = createPopupCloseButton(container, {innerText: "Cancel"}); - - //Make an object to keep track of what industries you're already in - var ownedIndustries = {} - for (var i = 0; i < this.divisions.length; ++i) { - ownedIndustries[this.divisions[i].type] = true; - } - - //Add industry types to selector - //Have Agriculture be first as recommended option - if (!ownedIndustries["Agriculture"]) { - selector.add(createElement("option", { - text:Industries["Agriculture"], value:"Agriculture" - })); - } - - for (var key in Industries) { - if (key !== "Agriculture" && Industries.hasOwnProperty(key) && !ownedIndustries[key]) { - var ind = Industries[key]; - selector.add(createElement("option", { - text: ind,value:key, - })); - } - } - - //Initial Industry Description - var ind = selector.options[selector.selectedIndex].value; - industryDescription.innerHTML = (IndustryDescriptions[ind] + "

"); - - //Change the industry description text based on selected option - selector.addEventListener("change", function() { - var ind = selector.options[selector.selectedIndex].value; - industryDescription.innerHTML = IndustryDescriptions[ind] + "

"; - }); - - //Add to DOM - content.appendChild(txt); - content.appendChild(selector); - content.appendChild(industryDescription); - content.appendChild(nameLabel); - content.appendChild(nameInput); - content.appendChild(noBtn); - content.appendChild(yesBtn); - container.appendChild(content); - document.getElementById("entire-game-container").appendChild(container); - container.style.display = "flex"; - nameInput.focus(); - return false; - } - })); - - headerTabs = companyManagementDiv.getElementsByClassName("cmpy-mgmt-header-tab"); -} - -//Updates UI to display which header tab is selected -Corporation.prototype.selectHeaderTab = function(currentTab) { - if (currentTab == null) {return;} - for (var i = 0; i < headerTabs.length; ++i) { - headerTabs[i].className = "cmpy-mgmt-header-tab"; - } - currentTab.className = "cmpy-mgmt-header-tab current"; -} - -Corporation.prototype.createDivisionUIHeaderTab = function(division) { - var tabId = "cmpy-mgmt-" + division.name + "-tab"; - var tab = createElement("button", { - id:tabId, - class:"cmpy-mgmt-header-tab", - innerText:division.name, - clickListener:()=>{ - this.selectHeaderTab(tab); - this.displayDivisionContent(division, Locations.Sector12); - return false; - } - }); - companyManagementHeaderTabs.appendChild(tab); -} - -Corporation.prototype.clearUIPanel = function() { - while(companyManagementPanel.firstChild) { - companyManagementPanel.removeChild(companyManagementPanel.firstChild); - } -} - -Corporation.prototype.updateUIContent = function() { - //Check which of the header tab buttons is checked - if (headerTabs == null) { - console.log("ERROR: headerTabs is null in Corporation.updateUIContent()"); - return; - } - for (var i = 0; i < headerTabs.length; ++i) { - if (headerTabs[i].classList.contains("current")) { - if (i === 0) { - //Corporation overview - this.updateCorporationOverviewContent(); - } else { - //Division - this.updateDivisionContent(this.divisions[i-1]); - } - return; - } - } -} - -Corporation.prototype.displayCorporationOverviewContent = function() { - this.clearUIPanel(); - companyManagementPanel.appendChild(createElement("p", { - id:"cmpy-mgmt-overview-text", - })); - if (headerTabs && headerTabs.length >= 1) { - this.selectHeaderTab(headerTabs[0]); - } - - //Check if player has Corporation Handbook - var homeComp = Player.getHomeComputer(), hasHandbook = false, - handbookFn = "corporation-management-handbook.lit"; - for (var i = 0; i < homeComp.messages.length; ++i) { - if (isString(homeComp.messages[i]) && homeComp.messages[i] === handbookFn) { - hasHandbook = true; - break; - } - } - - companyManagementPanel.appendChild(createElement("a", { - class:"a-link-button", innerText:"Getting Started Guide", display:"inline-block", - tooltip:"Get a copy of and read 'The Complete Handbook for Creating a Successful Corporation.' " + - "This is a .lit file that guides you through the beginning of setting up a Corporation and " + - "provides some tips/pointers for helping you get started with managing it.", - clickListener:()=>{ - if (!hasHandbook) {homeComp.messages.push(handbookFn);} - showLiterature(handbookFn); - return false; - } - })); - - //Investors - if (this.public) { - //Sell share buttons - var sellShares = createElement("a", { - class:"a-link-button tooltip", innerText:"Sell Shares", display:"inline-block", - clickListener: () => { - var popupId = "cmpy-mgmt-sell-shares-popup"; - var currentStockPrice = this.sharePrice; - var txt = createElement("p", { - innerHTML: "Enter the number of shares you would like to sell. The money from " + - "selling your shares will go directly to you (NOT your Corporation).

" + - "Selling your shares will cause your corporation's stock price to fall due to " + - "dilution. Furthermore, selling a large number of shares all at once will have an immediate effect " + - "in reducing your stock price.

" + - "The current price of your " + - "company's stock is " + numeralWrapper.format(currentStockPrice, "$0.000a"), - }); - var profitIndicator = createElement("p", {}); - var input = createElement("input", { - type:"number", placeholder:"Shares to sell", margin:"5px", - inputListener: ()=> { - var numShares = Math.round(input.value); - if (isNaN(numShares) || numShares <= 0) { - profitIndicator.innerText = "ERROR: Invalid value entered for number of shares to sell" - } else if (numShares > this.numShares) { - profitIndicator.innerText = "You don't have this many shares to sell!"; - } else { - const stockSaleResults = this.calculateShareSale(numShares); - const profit = stockSaleResults[0]; - const newSharePrice = stockSaleResults[1]; - const newSharesUntilUpdate = stockSaleResults[2]; - profitIndicator.innerText = "Sell " + numShares + " shares for a total of " + - numeralWrapper.format(profit, '$0.000a'); - } - } - }); - var confirmBtn = createElement("a", { - class:"a-link-button", innerText:"Sell shares", display:"inline-block", - clickListener:()=>{ - var shares = Math.round(input.value); - if (isNaN(shares) || shares <= 0) { - dialogBoxCreate("ERROR: Invalid value for number of shares"); - } else if (shares > this.numShares) { - dialogBoxCreate("ERROR: You don't have this many shares to sell"); - } else { - const stockSaleResults = this.calculateShareSale(shares); - const profit = stockSaleResults[0]; - const newSharePrice = stockSaleResults[1]; - const newSharesUntilUpdate = stockSaleResults[2]; - - this.numShares -= shares; - if (isNaN(this.issuedShares)) { - console.log("ERROR: Corporation issuedShares is NaN: " + this.issuedShares); - console.log("Converting to number now"); - var res = parseInt(this.issuedShares); - if (isNaN(res)) { - this.issuedShares = 0; - } else { - this.issuedShares = res; - } - } - this.issuedShares += shares; - this.sharePrice = newSharePrice; - this.shareSalesUntilPriceUpdate = newSharesUntilUpdate; - this.shareSaleCooldown = SellSharesCooldown; - Player.gainMoney(profit); - Player.recordMoneySource(profit, "corporation"); - removeElementById(popupId); - dialogBoxCreate(`Sold ${numeralWrapper.formatMoney(shares, "0.000a")} shares for ` + - `${numeralWrapper.formatMoney(profit, "$0.000a")}. ` + - `The corporation's stock price fell to ${numeralWrapper.formatMoney(this.sharePrice)} ` + - `as a result of dilution.`); - return false; - } - - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", innerText:"Cancel", display:"inline-block", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, profitIndicator, input, confirmBtn, cancelBtn]); - } - }); - - sellSharesButtonTooltip = createElement("span", { - class: "tooltiptext", - innerText: "Sell your shares in the company. The money earned from selling your " + - "shares goes into your personal account, not the Corporation's. " + - "This is one of the only ways to profit from your business venture.", - }); - sellShares.appendChild(sellSharesButtonTooltip); - - //Buyback shares button - var buybackShares = createElement("a", { - class:"a-link-button", innerText:"Buyback shares", display:"inline-block", - tooltip:"Buy back shares you that previously issued or sold at market price.", - clickListener:()=>{ - var popupId = "cmpy-mgmt-buyback-shares-popup"; - const currentStockPrice = this.sharePrice; - const buybackPrice = currentStockPrice * 1.1; - var txt = createElement("p", { - innerHTML: "Enter the number of outstanding shares you would like to buy back. " + - "These shares must be bought at a 10% premium. However, " + - "repurchasing shares from the market tends to lead to an increase in stock price.

" + - "To purchase these shares, you must use your own money (NOT your Corporation's funds).

" + - "The current buyback price of your company's stock is " + - numeralWrapper.format(buybackPrice, "$0.000a") + - ". Your company currently has " + formatNumber(this.issuedShares, 3) + " outstanding stock shares", - }); - var costIndicator = createElement("p", {}); - var input = createElement("input", { - type:"number", placeholder:"Shares to buyback", margin:"5px", - inputListener: ()=> { - var numShares = Math.round(input.value); - //TODO add conditional for if player doesn't have enough money - if (isNaN(numShares) || numShares <= 0) { - costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback" - } else if (numShares > this.issuedShares) { - costIndicator.innerText = "There are not this many shares available to buy back. " + - "There are only " + this.issuedShares + " outstanding shares."; - } else { - costIndicator.innerText = "Purchase " + numShares + " shares for a total of " + - numeralWrapper.format(numShares * buybackPrice, '$0.000a'); - } - } - }); - var confirmBtn = createElement("a", { - class:"a-link-button", innerText:"Buy shares", display:"inline-block", - clickListener:()=>{ - var shares = Math.round(input.value); - const tempStockPrice = this.sharePrice; - const buybackPrice = tempStockPrice * 1.1; - if (isNaN(shares) || shares <= 0) { - dialogBoxCreate("ERROR: Invalid value for number of shares"); - } else if (shares > this.issuedShares) { - dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back"); - } else if (shares * buybackPrice > Player.money) { - dialogBoxCreate("ERROR: You do not have enough money to purchase this many shares (you need " + - numeralWrapper.format(shares * buybackPrice, "$0.000a") + ")"); - } else { - this.numShares += shares; - if (isNaN(this.issuedShares)) { - console.log("ERROR: Corporation issuedShares is NaN: " + this.issuedShares); - console.log("Converting to number now"); - var res = parseInt(this.issuedShares); - if (isNaN(res)) { - this.issuedShares = 0; - } else { - this.issuedShares = res; - } - } - this.issuedShares -= shares; - Player.loseMoney(shares * buybackPrice); - removeElementById(popupId); - } - return false; - - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", - innerText:"Cancel", - display:"inline-block", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, costIndicator, input, confirmBtn, cancelBtn]); - } - }); - - companyManagementPanel.appendChild(sellShares); - companyManagementPanel.appendChild(buybackShares); - - sellSharesButton = sellShares; - - // Issue new Shares - appendLineBreaks(companyManagementPanel, 1); - const issueNewShares = createElement("a", { - class: "std-button tooltip", - display: "inline-block", - innerText: "Issue New Shares", - clickListener: () => { - const popupId = "cmpy-mgmt-issue-new-shares-popup"; - const maxNewSharesUnrounded = Math.round(this.totalShares * 0.2); - const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); - - const descText = createElement("p", { - innerHTML: "You can issue new equity shares (i.e. stocks) in order to raise " + - "capital for your corporation.

" + - ` * You can issue at most ${numeralWrapper.format(maxNewShares, "0.000a")} new shares
` + - ` * New shares are sold at a 10% discount
` + - ` * You can only issue new shares once every 12 hours
` + - ` * Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share
` + - ` * Number of new shares issued must be a multiple of 10 million

` + - `When you choose to issue new equity, private shareholders have first priority for up to 50% of the new shares. ` + - `If they choose to exercise this option, these newly issued shares become private, restricted shares, which means ` + - `you cannot buy them back.`, - }); - - let issueBtn, newSharesInput; - const dynamicText = createElement("p", { - display: "block", - }); - - function updateDynamicText(corp) { - const newSharePrice = Math.round(corp.sharePrice * 0.9); - let newShares = parseInt(newSharesInput.value); - if (isNaN(newShares)) { - dynamicText.innerText = "Invalid input"; - return; - } - - // Round to nearest ten-millionth - newShares /= 10e6; - newShares = Math.round(newShares) * 10e6; - - if (newShares < 10e6) { - dynamicText.innerText = "Must issue at least 10 million new shares"; - return; - } - - if (newShares > maxNewShares) { - dynamicText.innerText = "You cannot issue that many shares"; - return; - } - - dynamicText.innerText = `Issue ${numeralWrapper.format(newShares, "0.000a")} new shares ` + - `for ${numeralWrapper.formatMoney(newShares * newSharePrice)}?` - } - newSharesInput = createElement("input", { - margin: "5px", - placeholder: "# New Shares", - type: "number", - onkeyup: (e) => { - e.preventDefault(); - if (e.keyCode === KEY.ENTER) { - issueBtn.click(); - } else { - updateDynamicText(this); - } - } - }); - - issueBtn = createElement("a", { - class: "std-button", - display: "inline-block", - innerText: "Issue New Shares", - clickListener: () => { - const newSharePrice = Math.round(this.sharePrice * 0.9); - let newShares = parseInt(newSharesInput.value); - if (isNaN(newShares)) { - dialogBoxCreate("Invalid input for number of new shares"); - return; - } - - // Round to nearest ten-millionth - newShares = Math.round(newShares / 10e6) * 10e6; - - if (newShares < 10e6 || newShares > maxNewShares) { - dialogBoxCreate("Invalid input for number of new shares"); - return; - } - - const profit = newShares * newSharePrice; - this.issueNewSharesCooldown = IssueNewSharesCooldown; - this.totalShares += newShares; - - // Determine how many are bought by private investors - // Private investors get up to 50% at most - // Round # of private shares to the nearest millionth - let privateShares = getRandomInt(0, Math.round(newShares / 2)); - privateShares = Math.round(privateShares / 1e6) * 1e6; - - this.issuedShares += (newShares - privateShares); - this.funds = this.funds.plus(profit); - this.immediatelyUpdateSharePrice(); - - removeElementById(popupId); - dialogBoxCreate(`Issued ${numeralWrapper.format(newShares, "0.000a")} and raised ` + - `${numeralWrapper.formatMoney(profit)}. ${numeralWrapper.format(privateShares, "0.000a")} ` + - `of these shares were bought by private investors.

` + - `Stock price decreased to ${numeralWrapper.formatMoney(this.sharePrice)}`); - return false; - } - }); - - const cancelBtn = createPopupCloseButton(popupId, { - class: "std-button", - display: "inline-block", - innerText: "Cancel", - }); - - createPopup(popupId, [descText, dynamicText, newSharesInput, issueBtn, cancelBtn]); - newSharesInput.focus(); - } - }); - issueNewSharesButtonTooltip = createElement("span", { - class: "tooltiptext", - innerText: "Issue new equity shares to raise capital", - }); - issueNewShares.appendChild(issueNewSharesButtonTooltip); - - companyManagementPanel.appendChild(issueNewShares); - - issueNewSharesButton = issueNewShares; - - // Set Stock Dividends - const issueDividends = createElement("a", { - class: "std-button", - display: "inline-block", - innerText: "Issue Dividends", - tooltip: "Manage the dividends that are paid out to shareholders (including yourself)", - clickListener: () => { - const popupId = "cmpy-mgmt-issue-dividends-popup"; - const descText = "Dividends are a distribution of a portion of the corporation's " + - "profits to the shareholders. This includes yourself, as well.

" + - "In order to issue dividends, simply allocate some percentage " + - "of your corporation's profits to dividends. This percentage must be an " + - `integer between 0 and ${DividendMaxPercentage}. (A percentage of 0 means no dividends will be ` + - "issued

" + - "Two important things to note:
" + - " * Issuing dividends will negatively affect your corporation's stock price
" + - " * Dividends are taxed. Taxes start at 50%, but can be decreased

" + - "Example: Assume your corporation makes $100m / sec in profit and you allocate " + - "40% of that towards dividends. That means your corporation will gain $60m / sec " + - "in funds and the remaining $40m / sec will be paid as dividends. Since your " + - "corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share " + - "per second before taxes."; - const txt = createElement("p", { innerHTML: descText, }); - - let allocateBtn; - const dividendPercentInput = createElement("input", { - margin: "5px", - placeholder: "Dividend %", - type: "number", - onkeyup: (e) => { - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {allocateBtn.click();} - } - }); - - allocateBtn = createElement("button", { - class: "std-button", - display: "inline-block", - innerText: "Allocate Dividend Percentage", - clickListener: () => { - const percentage = Math.round(parseInt(dividendPercentInput.value)); - if (isNaN(percentage) || percentage < 0 || percentage > DividendMaxPercentage) { - return dialogBoxCreate(`Invalid value. Must be an integer between 0 and ${DividendMaxPercentage}`); - } - - this.dividendPercentage = percentage; - - removeElementById(popupId); - return false; - } - }); - - const cancelBtn = createPopupCloseButton(popupId, { - class: "std-button", - display: "inline-block", - innerText: "Cancel", - }); - - createPopup(popupId, [txt, dividendPercentInput, allocateBtn, cancelBtn]); - dividendPercentInput.focus(); - }, - }); - companyManagementPanel.appendChild(issueDividends); - } else { - var findInvestors = createElement("a", { - class: this.fundingRound >= 4 ? "a-link-button-inactive" : "a-link-button tooltip", - innerText: "Find Investors", - display:"inline-block", - clickListener:()=>{ - this.getInvestment(); - } - }); - if (this.fundingRound < 4) { - var findInvestorsTooltip = createElement("span", { - class:"tooltiptext", - innerText:"Search for private investors who will give you startup funding in exchange " + - "for equity (stock shares) in your company" - }); - findInvestors.appendChild(findInvestorsTooltip); - } - - var goPublic = createElement("a", { - class:"a-link-button tooltip", - innerText:"Go Public", - display:"inline-block", - clickListener:()=>{ - this.goPublic(); - return false; - } - }); - var goPublicTooltip = createElement("span", { - class:"tooltiptext", - innerText: "Become a publicly traded and owned entity. Going public involves " + - "issuing shares for an IPO. Once you are a public company, " + - "your shares will be traded on the stock market." - }); - goPublic.appendChild(goPublicTooltip); - - companyManagementPanel.appendChild(findInvestors); - companyManagementPanel.appendChild(goPublic); - } - - appendLineBreaks(companyManagementPanel, 1); - - //If your Corporation is big enough, buy faction influence through bribes - var canBribe = this.determineValuation() >= BribeThreshold; - var bribeFactions = createElement("a", { - class: canBribe ? "a-link-button" : "a-link-button-inactive", - innerText:"Bribe Factions", display:"inline-block", - tooltip:canBribe - ? "Use your Corporations power and influence to bribe Faction leaders in exchange for reputation" - : "Your Corporation is not powerful enough to bribe Faction leaders", - clickListener:()=>{ - var popupId = "cmpy-mgmt-bribe-factions-popup"; - var txt = createElement("p", { - innerText:"You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation" - }); - var factionSelector = createElement("select", {margin:"3px"}); - for (var i = 0; i < Player.factions.length; ++i) { - var facName = Player.factions[i]; - factionSelector.add(createElement("option", { - text:facName, value:facName - })); - } - var repGainText = createElement("p"); - var stockSharesInput; - var moneyInput = createElement("input", { - type:"number", placeholder:"Corporation funds", margin:"5px", - inputListener:()=>{ - var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); - var stockPrice = this.sharePrice; - var stockShares = stockSharesInput.value == null || stockSharesInput.value == "" ? 0 : Math.round(parseFloat(stockSharesInput.value)); - if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { - repGainText.innerText = "ERROR: Invalid value(s) entered"; - } else if (this.funds.lt(money)) { - repGainText.innerText = "ERROR: You do not have this much money to bribe with"; - } else if (this.stockShares > this.numShares) { - repGainText.innerText = "ERROR: You do not have this many shares to bribe with"; - } else { - - var totalAmount = Number(money) + (stockShares * stockPrice); - var repGain = totalAmount / BribeToRepRatio; - repGainText.innerText = "You will gain " + formatNumber(repGain, 0) + - " reputation with " + - factionSelector.options[factionSelector.selectedIndex].value + - " with this bribe"; - } - } - }); - stockSharesInput = createElement("input", { - type:"number", placeholder:"Stock Shares", margin: "5px", - inputListener:()=>{ - var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); - var stockPrice = this.sharePrice; - var stockShares = stockSharesInput.value == null || stockSharesInput.value == "" ? 0 : Math.round(stockSharesInput.value); - if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { - repGainText.innerText = "ERROR: Invalid value(s) entered"; - } else if (this.funds.lt(money)) { - repGainText.innerText = "ERROR: You do not have this much money to bribe with"; - } else if (this.stockShares > this.numShares) { - repGainText.innerText = "ERROR: You do not have this many shares to bribe with"; - } else { - var totalAmount = money + (stockShares * stockPrice); - var repGain = totalAmount / BribeToRepRatio; - console.log("repGain: " + repGain); - repGainText.innerText = "You will gain " + formatNumber(repGain, 0) + - " reputation with " + - factionSelector.options[factionSelector.selectedIndex].value + - " with this bribe"; - } - } - }); - var confirmButton = createElement("a", { - class:"a-link-button", innerText:"Bribe", display:"inline-block", - clickListener:()=>{ - var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); - var stockPrice = this.sharePrice; - var stockShares = stockSharesInput.value == null || stockSharesInput.value == ""? 0 : Math.round(parseFloat(stockSharesInput.value)); - var fac = Factions[factionSelector.options[factionSelector.selectedIndex].value]; - if (fac == null) { - dialogBoxCreate("ERROR: You must select a faction to bribe"); - return false; - } - if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { - dialogBoxCreate("ERROR: Invalid value(s) entered"); - } else if (this.funds.lt(money)) { - dialogBoxCreate("ERROR: You do not have this much money to bribe with"); - } else if (stockShares > this.numShares) { - dialogBoxCreate("ERROR: You do not have this many shares to bribe with"); - } else { - var totalAmount = money + (stockShares * stockPrice); - var repGain = totalAmount / BribeToRepRatio; - dialogBoxCreate("You gained " + formatNumber(repGain, 0) + - " reputation with " + fac.name + " by bribing them."); - fac.playerReputation += repGain; - this.funds = this.funds.minus(money); - this.numShares -= stockShares; - removeElementById(popupId); - return false; - } - } - }); - var cancelButton = createElement("a", { - class:"a-link-button", innerText:"Cancel", display:"inline-block", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - - createPopup(popupId, [txt, factionSelector, repGainText, - moneyInput, stockSharesInput, confirmButton, cancelButton]); - } - }); - companyManagementPanel.appendChild(bribeFactions); - - //Update overview text - this.updateCorporationOverviewContent(); - - //Don't show upgrades if player hasn't opened any divisions - if (this.divisions.length <= 0) {return; } - //Corporation Upgrades - var upgradeContainer = createElement("div", { - class:"cmpy-mgmt-upgrade-container", - }); - upgradeContainer.appendChild(createElement("h1", { - innerText:"Unlocks", margin:"6px", padding:"6px", - })); - - //Unlock upgrades - var corp = this; - var numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length, - numUpgrades = Object.keys(CorporationUpgrades).length; - if (this.unlockUpgrades == null || this.upgrades == null) { //Backwards compatibility - this.unlockUpgrades = Array(numUnlockUpgrades).fill(0); - this.upgrades = Array(numUpgrades).fill(0); - } - while (this.unlockUpgrades.length < numUnlockUpgrades) {this.unlockUpgrades.push(0);} - while (this.upgrades.length < numUpgrades) {this.upgrades.push(0);} - while (this.upgradeMultipliers < numUpgrades) {this.upgradeMultipliers.push(1);} - - for (var i = 0; i < numUnlockUpgrades; ++i) { - (function(i, corp) { - if (corp.unlockUpgrades[i] === 0) { - var upgrade = CorporationUnlockUpgrades[i.toString()]; - if (upgrade == null) { - console.log("ERROR: Could not find upgrade index " + i); - return; - } - - upgradeContainer.appendChild(createElement("div", { - class:"cmpy-mgmt-upgrade-div", width:"45%", - innerHTML:upgrade[2] + " - " + numeralWrapper.format(upgrade[1], "$0.000a"), - tooltip: upgrade[3], - clickListener:()=>{ - if (corp.funds.lt(upgrade[1])) { - dialogBoxCreate("Insufficient funds"); - } else { - corp.unlock(upgrade); - corp.displayCorporationOverviewContent(); - } - } - })); - } - })(i, corp); - } - - //Levelable upgrades - upgradeContainer.appendChild(createElement("h1", { - innerText:"Upgrades", margin:"6px", padding:"6px", - })); - - for (var i = 0; i < numUpgrades; ++i) { - (function(i, corp) { - var upgrade = CorporationUpgrades[i.toString()]; - if (upgrade == null) { - console.log("ERROR: Could not find levelable upgrade index " + i); - return; - } - - var baseCost = upgrade[1], priceMult = upgrade[2]; - var cost = baseCost * Math.pow(priceMult, corp.upgrades[i]); - upgradeContainer.appendChild(createElement("div", { - class:"cmpy-mgmt-upgrade-div", width:"45%", - innerHTML:upgrade[4] + " - " + numeralWrapper.format(cost, "$0.000a"), - tooltip:upgrade[5], - clickListener:()=>{ - if (corp.funds.lt(cost)) { - dialogBoxCreate("Insufficient funds"); - } else { - corp.upgrade(upgrade); - corp.displayCorporationOverviewContent(); - } - } - })); - })(i, corp); - } - - companyManagementPanel.appendChild(upgradeContainer); -} - -Corporation.prototype.updateCorporationOverviewContent = function() { - var p = document.getElementById("cmpy-mgmt-overview-text"); - if (p == null) { - console.log("WARNING: Could not find overview text elemtn in updateCorporationOverviewContent()"); - return; - } - - // Formatted text for profit - var profit = this.revenue.minus(this.expenses).toNumber(), - profitStr = profit >= 0 ? numeralWrapper.format(profit, "$0.000a") : "-" + numeralWrapper.format(-1 * profit, "$0.000a"); - - // Formatted text for dividend information, if applicable - let dividendStr = ""; - if (this.dividendPercentage > 0 && profit > 0) { - const totalDividends = (this.dividendPercentage / 100) * profit; - const retainedEarnings = profit - totalDividends; - const dividendsPerShare = totalDividends / this.totalShares; - const playerEarnings = this.numShares * dividendsPerShare; - - dividendStr = `Retained Profits (after dividends): ${numeralWrapper.format(retainedEarnings, "$0.000a")} / s

` + - `Dividend Percentage: ${numeralWrapper.format(this.dividendPercentage / 100, "0%")}
` + - `Dividends per share: ${numeralWrapper.format(dividendsPerShare, "$0.000a")} / s
` + - `Your earnings as a shareholder (Pre-Tax): ${numeralWrapper.format(playerEarnings, "$0.000a")} / s
` + - `Dividend Tax Rate: ${this.dividendTaxPercentage}%
` + - `Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (this.dividendTaxPercentage / 100)), "$0.000a")} / s

`; - } - - var txt = "Total Funds: " + numeralWrapper.format(this.funds.toNumber(), '$0.000a') + "
" + - "Total Revenue: " + numeralWrapper.format(this.revenue.toNumber(), "$0.000a") + " / s
" + - "Total Expenses: " + numeralWrapper.format(this.expenses.toNumber(), "$0.000a") + "/ s
" + - "Total Profits: " + profitStr + " / s
" + - dividendStr + - "Publicly Traded: " + (this.public ? "Yes" : "No") + "
" + - "Owned Stock Shares: " + numeralWrapper.format(this.numShares, '0.000a') + "
" + - "Stock Price: " + (this.public ? "$" + formatNumber(this.sharePrice, 2) : "N/A") + "
" + - "

Total Stock Shares: " + numeralWrapper.format(this.totalShares, "0.000a") + - "" + - `Outstanding Shares: ${numeralWrapper.format(this.issuedShares, "0.000a")}
` + - `Private Shares: ${numeralWrapper.format(this.totalShares - this.issuedShares - this.numShares, "0.000a")}` + - "



"; - - const storedTime = this.storedCycles * CONSTANTS.MilliPerCycle / 1000; - if (storedTime > 15) { - txt += `Bonus Time: ${storedTime} seconds

`; - } - - var prodMult = this.getProductionMultiplier(), - storageMult = this.getStorageMultiplier(), - advMult = this.getAdvertisingMultiplier(), - empCreMult = this.getEmployeeCreMultiplier(), - empChaMult = this.getEmployeeChaMultiplier(), - empIntMult = this.getEmployeeIntMultiplier(), - empEffMult = this.getEmployeeEffMultiplier(), - salesMult = this.getSalesMultiplier(), - sciResMult = this.getScientificResearchMultiplier(); - if (prodMult > 1) {txt += "Production Multiplier: " + formatNumber(prodMult, 3) + "
";} - if (storageMult > 1) {txt += "Storage Multiplier: " + formatNumber(storageMult, 3) + "
";} - if (advMult > 1) {txt += "Advertising Multiplier: " + formatNumber(advMult, 3) + "
";} - if (empCreMult > 1) {txt += "Empl. Creativity Multiplier: " + formatNumber(empCreMult, 3) + "
";} - if (empChaMult > 1) {txt += "Empl. Charisma Multiplier: " + formatNumber(empChaMult, 3) + "
";} - if (empIntMult > 1) {txt += "Empl. Intelligence Multiplier: " + formatNumber(empIntMult, 3) + "
";} - if (empEffMult > 1) {txt += "Empl. Efficiency Multiplier: " + formatNumber(empEffMult, 3) + "
";} - if (salesMult > 1) {txt += "Sales Multiplier: " + formatNumber(salesMult, 3) + "
";} - if (sciResMult > 1) {txt += "Scientific Research Multiplier: " + formatNumber(sciResMult, 3) + "
";} - p.innerHTML = txt; - - // Disable buttons for cooldowns - if (sellSharesButton instanceof Element) { - if (this.shareSaleCooldown <= 0) { - sellSharesButton.className = "std-button tooltip"; - } else { - sellSharesButton.className = "a-link-button-inactive tooltip"; - } - } - - if (sellSharesButtonTooltip instanceof Element) { - if (this.shareSaleCooldown <= 0) { - sellSharesButtonTooltip.innerText = "Sell your shares in the company. The money earned from selling your " + - "shares goes into your personal account, not the Corporation's. " + - "This is one of the only ways to profit from your business venture."; - } else { - sellSharesButtonTooltip.innerText = "Cannot sell shares for " + this.convertCooldownToString(this.shareSaleCooldown); - } - } - - if (issueNewSharesButton instanceof Element) { - if (this.issueNewSharesCooldown <= 0) { - issueNewSharesButton.className = "std-button tooltip"; - } else { - issueNewSharesButton.className = "a-link-button-inactive tooltip"; - } - } - - if (issueNewSharesButtonTooltip instanceof Element) { - if (this.issueNewSharesCooldown <= 0) { - issueNewSharesButtonTooltip.innerText = "Issue new equity shares to raise capital" - } else { - issueNewSharesButtonTooltip.innerText = "Cannot issue new shares for " + this.convertCooldownToString(this.issueNewSharesCooldown); - } - } -} - -Corporation.prototype.displayDivisionContent = function(division, city) { - this.clearUIPanel(); - currentCityUi = city; - - //Add the city tabs on the left - for (var cityName in division.offices) { - if (division.offices[cityName] instanceof OfficeSpace) { - this.createCityUITab(cityName, division); - } - } - cityTabs = companyManagementPanel.getElementsByClassName("cmpy-mgmt-city-tab"); - if (cityTabs.length > 0) { - this.selectCityTab(document.getElementById("cmpy-mgmt-city-" + city + "-tab"), city); - } - - //Expand into new City button - companyManagementPanel.appendChild(createElement("button", { - class:"cmpy-mgmt-city-tab", innerText:"Expand into new City", display:"inline-block", - clickListener:()=>{ - var popupId = "cmpy-mgmt-expand-city-popup"; - var text = createElement("p", { - innerText: "Would you like to expand into a new city by opening an office? " + - "This would cost " + numeralWrapper.format(OfficeInitialCost, '$0.000a'), - }); - var citySelector = createElement("select", {class: "dropdown", margin:"5px"}); - for (var cityName in division.offices) { - if (division.offices.hasOwnProperty(cityName)) { - if (!(division.offices[cityName] instanceof OfficeSpace)) { - citySelector.add(createElement("option", { - text: cityName, - value: cityName - })); - } - } - } - - var confirmBtn = createElement("a", { - innerText:"Confirm", class:"a-link-button", display:"inline-block", margin:"3px", - clickListener:()=>{ - var city = citySelector.options[citySelector.selectedIndex].value; - if (this.funds.lt(OfficeInitialCost)) { - dialogBoxCreate("You don't have enough company funds to open a new office!"); - } else { - this.funds = this.funds.minus(OfficeInitialCost); - dialogBoxCreate("Opened a new office in " + city + "!"); - division.offices[city] = new OfficeSpace({ - loc:city, - size:OfficeInitialSize, - }); - this.displayDivisionContent(division, city); - } - removeElementById(popupId); - return false; - } - }); - const cancelBtn = createPopupCloseButton(popupId, { - class: "std-button", - innerText: "Cancel", - }); - - createPopup(popupId, [text, citySelector, confirmBtn, cancelBtn]); - return false; - } - })); - companyManagementPanel.appendChild(createElement("br", {})); // Force line break - - //Get office object - var office = division.offices[currentCityUi]; - if (!(office instanceof OfficeSpace)) { - console.log("ERROR: Current city for UI does not have an office space"); - return; - } - - //Left and right panels - var leftPanel = createElement("div", { - class: "cmpy-mgmt-industry-left-panel", - overflow: "visible", - padding: "2px", - }); - var rightPanel = createElement("div", { - class: "cmpy-mgmt-industry-right-panel", - overflow: "visible", - padding: "2px", - }); - companyManagementPanel.appendChild(leftPanel); - companyManagementPanel.appendChild(rightPanel); - - //Different sections (Overview, Employee/Office, and Warehouse) - industryOverviewPanel = createElement("div", { - id:"cmpy-mgmt-industry-overview-panel", class:"cmpy-mgmt-industry-overview-panel" - }); - leftPanel.appendChild(industryOverviewPanel); - - industryEmployeePanel = createElement("div", { - id:"cmpy-mgmt-employee-panel", class:"cmpy-mgmt-employee-panel" - }); - leftPanel.appendChild(industryEmployeePanel); - - industryWarehousePanel = createElement("div", { - id:"cmpy-mgmt-warehouse-panel", class:"cmpy-mgmt-warehouse-panel" - }); - rightPanel.appendChild(industryWarehousePanel); - - //Industry overview text - industryOverviewText = createElement("p", {}); - industryOverviewPanel.appendChild(industryOverviewText); - industryOverviewPanel.appendChild(createElement("br", {})); - - //Industry overview Purchases & Upgrades - var numUpgrades = Object.keys(IndustryUpgrades).length; - while (division.upgrades.length < numUpgrades) {division.upgrades.push(0);} //Backwards compatibility - - var industryOverviewUpgrades = createElement("div", {}); - industryOverviewUpgrades.appendChild(createElement("u", { - innerText:"Purchases & Upgrades", margin:"2px", padding:"2px", - fontSize:"14px", - })); - industryOverviewUpgrades.appendChild(createElement("br", {})); - for (let i = 0; i < numUpgrades; ++i) { - if (division.hasResearch("AutoBrew") && i == 0) { - continue; // AutoBrew disables Coffee upgrades, which is index 0 - } - (function(i, corp, division, office) { - var upgrade = IndustryUpgrades[i.toString()]; - if (upgrade == null) { - console.log("ERROR: Could not find levelable upgrade index: " + i); - return; - } - - var baseCost = upgrade[1], priceMult = upgrade[2], cost = 0; - switch(i) { - case 0: //Coffee, cost is static per employee - cost = office.employees.length * baseCost; - break; - default: - cost = baseCost * Math.pow(priceMult, division.upgrades[i]); - break; - } - industryOverviewUpgrades.appendChild(createElement("div", { - class:"cmpy-mgmt-upgrade-div", display:"inline-block", - innerHTML:upgrade[4] + ' - ' + numeralWrapper.format(cost, "$0.000a"), - tooltip:upgrade[5], - clickListener:()=>{ - if (corp.funds.lt(cost)) { - dialogBoxCreate("Insufficient funds"); - } else { - corp.funds = corp.funds.minus(cost); - division.upgrade(upgrade, { - corporation:corp, - office:office, - }); - corp.displayDivisionContent(division, city); - } - } - })); - industryOverviewUpgrades.appendChild(createElement("br", {})); - - })(i, this, division, office); - } - - - industryOverviewPanel.appendChild(industryOverviewUpgrades); - - //Industry Overview 'Create Product' button if applicable - if (division.makesProducts) { - //Get the text on the button based on Industry type - var createProductButtonText, createProductPopupText; - switch(division.type) { - case Industries.Food: - createProductButtonText = "Build Restaurant"; - createProductPopupText = "Build and manage a new restaurant!" - break; - case Industries.Tobacco: - createProductButtonText = "Create Product"; - createProductPopupText = "Create a new tobacco product!"; - break; - case Industries.Pharmaceutical: - createProductButtonText = "Create Drug"; - createProductPopupText = "Design and develop a new pharmaceutical drug!"; - break; - case Industries.Computer: - case "Computer": - createProductButtonText = "Create Product"; - createProductPopupText = "Design and manufacture a new computer hardware product!"; - break; - case Industries.Robotics: - createProductButtonText = "Design Robot"; - createProductPopupText = "Design and create a new robot or robotic system!"; - break; - case Industries.Software: - createProductButtonText = "Develop Software"; - createProductPopupText = "Develop a new piece of software!"; - break; - case Industries.Healthcare: - createProductButtonText = "Build Hospital"; - createProductPopupText = "Build and manage a new hospital!"; - break; - case Industries.RealEstate: - createProductButtonText = "Develop Property"; - createProductPopupText = "Develop a new piece of real estate property!"; - break; - default: - createProductButtonText = "Create Product"; - return ""; - } - createProductPopupText += "

To begin developing a product, " + - "first choose the city in which it will be designed. The stats of your employees " + - "in the selected city affect the properties of the finished product, such as its " + - "quality, performance, and durability.

" + - "You can also choose to invest money in the design and marketing of " + - "the product. Investing money in its design will result in a superior product. " + - "Investing money in marketing the product will help the product's sales."; - - //Create the button - industryOverviewPanel.appendChild(createElement("a", { - class:"a-link-button", innerText:createProductButtonText, margin:"6px", display:"inline-block", - clickListener:()=>{ - var popupId = "cmpy-mgmt-create-product-popup"; - var txt = createElement("p", { - innerHTML:createProductPopupText, - }); - var designCity = createElement("select", {}); - for (var cityName in division.offices) { - if (division.offices[cityName] instanceof OfficeSpace) { - designCity.add(createElement("option", { - value:cityName, - text:cityName - })); - } - } - var foo = "Product Name"; - if (division.type === Industries.Food) { - foo = "Restaurant Name"; - } else if (division.type === Industries.Healthcare) { - foo = "Hospital Name"; - } else if (division.type === Industries.RealEstate) { - foo = "Property Name"; - } - var productNameInput = createElement("input", { - placeholder:foo, - }); - var lineBreak1 = createElement("br",{}); - var designInvestInput = createElement("input", { - type:"number", - placeholder:"Design investment" - }); - var marketingInvestInput = createElement("input", { - type:"number", - placeholder:"Marketing investment" - }); - var confirmBtn = createElement("a", { - class:"a-link-button", - innerText:"Develop Product", - clickListener:()=>{ - if (designInvestInput.value == null) {designInvestInput.value = 0;} - if (marketingInvestInput.value == null) {marketingInvestInput.value = 0;} - var designInvest = parseFloat(designInvestInput.value), - marketingInvest = parseFloat(marketingInvestInput.value); - if (productNameInput.value == null || productNameInput.value === "") { - dialogBoxCreate("You must specify a name for your product!"); - } else if (isNaN(designInvest)) { - dialogBoxCreate("Invalid value for design investment"); - } else if (isNaN(marketingInvest)) { - dialogBoxCreate("Invalid value for marketing investment"); - } else if (this.funds.lt(designInvest + marketingInvest)) { - dialogBoxCreate("You don't have enough company funds to make this large of an investment"); - } else { - var product = new Product({ - name:productNameInput.value.replace(/[<>]/g, ''), //Sanitize for HTMl elements - createCity:designCity.options[designCity.selectedIndex].value, - designCost: designInvest, - advCost: marketingInvest, - }); - this.funds = this.funds.minus(designInvest + marketingInvest); - division.products[product.name] = product; - removeElementById(popupId); - } - //this.updateUIContent(); - this.displayDivisionContent(division, city); - return false; - } - }) - var cancelBtn = createElement("a", { - class:"a-link-button", - innerText:"Cancel", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }) - createPopup(popupId, [txt, designCity, productNameInput, lineBreak1, - designInvestInput, marketingInvestInput, confirmBtn, cancelBtn]); - } - })); - } - - //Employee and Office Panel - industryEmployeeText = createElement("p", { - id: "cmpy-mgmt-employee-p", - display:"block", - innerHTML: "

Office Space


" + - "Size: " + office.employees.length + " / " + office.size + " employees", - }); - industryEmployeePanel.appendChild(industryEmployeeText); - - //Hire Employee button - if (office.employees.length === 0) { - industryEmployeeHireButton = createElement("a", { - class:"a-link-button",display:"inline-block", - innerText:"Hire Employee", fontSize:"13px", - tooltip:"You'll need to hire some employees to get your operations started! " + - "It's recommended to have at least one employee in every position", - clickListener:()=>{ - office.findEmployees({corporation:this, industry:division}); - return false; - } - }); - //industryEmployeeHireButton.classList.add("flashing-button"); - } else { - industryEmployeeHireButton = createElement("a", { - class:"a-link-button",display:"inline-block", - innerText:"Hire Employee", fontSize:"13px", - clickListener:()=>{ - office.findEmployees({corporation:this, industry:division}); - return false; - } - }); - } - industryEmployeePanel.appendChild(industryEmployeeHireButton); - - //Autohire Employee button - industryEmployeeAutohireButton = createElement("a", { - class:"a-link-button", display:"inline-block", - innerText:"Autohire Employee", fontSize:"13px", - tooltip:"Automatically hires an employee and gives him/her a random name", - clickListener:()=>{ - office.hireRandomEmployee({corporation:this, industry:division}); - return false; - } - }); - industryEmployeePanel.appendChild(industryEmployeeAutohireButton); - - //Upgrade Office Size button - industryEmployeePanel.appendChild(createElement("br", {})); - industryOfficeUpgradeSizeButton = createElement("a", { - class:"a-link-button", innerText:"Upgrade size", - display:"inline-block", margin:"6px", fontSize:"13px", - tooltip:"Upgrade the office's size so that it can hold more employees!", - clickListener:()=>{ - var popupId = "cmpy-mgmt-upgrade-office-size-popup"; - var initialPriceMult = Math.round(office.size / OfficeInitialSize); - var upgradeCost = OfficeInitialCost * Math.pow(1.07, initialPriceMult); - - //Calculate cost to upgrade size by 15 employees - var mult = 0; - for (var i = 0; i < 5; ++i) { - mult += (Math.pow(1.07, initialPriceMult + i)); - } - var upgradeCost15 = OfficeInitialCost * mult; - - //Calculate max upgrade size and cost - var maxMult = (this.funds.dividedBy(OfficeInitialCost)).toNumber(); - var maxNum = 1; - mult = Math.pow(1.07, initialPriceMult); - while(maxNum < 50) { //Hard cap of 50x (extra 150 employees) - if (mult >= maxMult) {break;} - var multIncrease = Math.pow(1.07, initialPriceMult + maxNum); - if (mult + multIncrease > maxMult) { - break; - } else { - mult += multIncrease; - } - ++maxNum; - } - - var upgradeCostMax = OfficeInitialCost * mult; - - var text = createElement("p", { - innerText:"Increase the size of your office space to fit additional employees!" - }); - var text2 = createElement("p", {innerText: "Upgrade size: "}); - - var confirmBtn = createElement("a", { - class: this.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button", - display:"inline-block", margin:"4px", innerText:"by 3", - tooltip:numeralWrapper.format(upgradeCost, "$0.000a"), - clickListener:()=>{ - if (this.funds.lt(upgradeCost)) { - dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); - } else { - office.size += OfficeInitialSize; - this.funds = this.funds.minus(upgradeCost); - dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); - this.updateUIContent(); - } - removeElementById(popupId); - return false; - } - }); - var confirmBtn15 = createElement("a", { - class: this.funds.lt(upgradeCost15) ? "a-link-button-inactive" : "a-link-button", - display:"inline-block", margin:"4px", innerText:"by 15", - tooltip:numeralWrapper.format(upgradeCost15, "$0.000a"), - clickListener:()=>{ - if (this.funds.lt(upgradeCost15)) { - dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); - } else { - office.size += (OfficeInitialSize * 5); - this.funds = this.funds.minus(upgradeCost15); - dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); - this.updateUIContent(); - } - removeElementById(popupId); - return false; - } - }); - var confirmBtnMax = createElement("a", { - class:this.funds.lt(upgradeCostMax) ? "a-link-button-inactive" : "a-link-button", - display:"inline-block", margin:"4px", innerText:"by MAX (" + maxNum*OfficeInitialSize + ")", - tooltip:numeralWrapper.format(upgradeCostMax, "$0.000a"), - clickListener:()=>{ - if (this.funds.lt(upgradeCostMax)) { - dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); - } else { - office.size += (OfficeInitialSize * maxNum); - this.funds = this.funds.minus(upgradeCostMax); - dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); - this.updateUIContent(); - } - removeElementById(popupId); - return false; - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", innerText:"Cancel", display:"inline-block", margin:"4px", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }) - createPopup(popupId, [text, text2, confirmBtn, confirmBtn15, confirmBtnMax, cancelBtn]); - return false; - } - }); - industryEmployeePanel.appendChild(industryOfficeUpgradeSizeButton); - - //Throw Office Party - if (!division.hasResearch("AutoPartyManager")) { - industryEmployeePanel.appendChild(createElement("a", { - class:"a-link-button", display:"inline-block", innerText:"Throw Party", - fontSize:"13px", - tooltip:"Throw an office party to increase your employee's morale and happiness", - clickListener:()=>{ - var popupId = "cmpy-mgmt-throw-office-party-popup"; - var txt = createElement("p", { - innerText:"Enter the amount of money you would like to spend PER EMPLOYEE " + - "on this office party" - }); - var totalCostTxt = createElement("p", { - innerText:"Throwing this party will cost a total of $0" - }); - var confirmBtn; - var input = createElement("input", { - type:"number", margin:"5px", placeholder:"$ / employee", - inputListener:()=>{ - if (isNaN(input.value) || input.value < 0) { - totalCostTxt.innerText = "Invalid value entered!" - } else { - var totalCost = input.value * office.employees.length; - totalCostTxt.innerText = "Throwing this party will cost a total of " + numeralWrapper.format(totalCost, '$0.000a'); - } - }, - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - confirmBtn = createElement("a", { - class:"a-link-button", - display:"inline-block", - innerText:"Throw Party", - clickListener:()=>{ - if (isNaN(input.value) || input.value < 0) { - dialogBoxCreate("Invalid value entered"); - } else { - var totalCost = input.value * office.employees.length; - if (this.funds.lt(totalCost)) { - dialogBoxCreate("You don't have enough company funds to throw this party!"); - } else { - this.funds = this.funds.minus(totalCost); - var mult; - for (var fooit = 0; fooit < office.employees.length; ++fooit) { - mult = office.employees[fooit].throwParty(input.value); - } - dialogBoxCreate("You threw a party for the office! The morale and happiness " + - "of each employee increased by " + formatNumber((mult-1) * 100, 2) + "%."); - removeElementById(popupId); - } - } - return false; - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", - display:"inline-block", - innerText:"Cancel", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, totalCostTxt, input, confirmBtn, cancelBtn]); - } - })); - } - - industryEmployeeManagementUI = createElement("div", {}); - industryEmployeeInfo = createElement("p", {margin:"4px", padding:"4px"}); - if (empManualAssignmentModeActive) { - //Employees manually assigned - industryEmployeeManagementUI.appendChild(createElement("a", { - class:"a-link-button", display:"inline-block", margin:"4px", - innerText:"Switch to Auto Mode", - tooltip:"Switch to Automatic Assignment Mode, which will automatically " + - "assign employees to your selected jobs. You simply have to select " + - "the number of assignments for each job", - clickListener:()=>{ - empManualAssignmentModeActive = false; - this.displayDivisionContent(division, city); - } - })); - industryEmployeeManagementUI.appendChild(createElement("br", {})); - - industryIndividualEmployeeInfo = createElement("div", {margin:"4px", padding:"4px"}); - var selector = createElement("select", { - color: "white", backgroundColor:"black", margin:"4px", padding:"4px", - changeListener:()=>{ - var name = selector.options[selector.selectedIndex].text; - for (var i = 0; i < office.employees.length; ++i) { - if (office.employees[i].name === name) { - removeChildrenFromElement(industryIndividualEmployeeInfo); - office.employees[i].createUI(industryIndividualEmployeeInfo, this, division); - return; - } - } - console.log("ERROR: Employee in selector could not be found"); - } - }); - - for (var i = 0; i < office.employees.length; ++i) { - selector.add(createElement("option", {text:office.employees[i].name})); - } - - selector.selectedIndex = -1; - - industryEmployeeManagementUI.appendChild(industryEmployeeInfo); - industryEmployeeManagementUI.appendChild(selector); - industryEmployeeManagementUI.appendChild(industryIndividualEmployeeInfo); - } else { - //Player only manages the number of each occupation, not who gets what job - industryEmployeeManagementUI.appendChild(createElement("a", { - class:"a-link-button", display:"inline-block", margin:"4px", - innerText:"Switch to Manual Mode", - tooltip:"Switch to Manual Assignment Mode, which allows you to " + - "specify which employees should get which jobs", - clickListener:()=>{ - empManualAssignmentModeActive = true; - this.displayDivisionContent(division, city); - } - })); - industryEmployeeManagementUI.appendChild(createElement("br", {})); - - var opCount = 0, engCount = 0, busCount = 0, - mgmtCount = 0, rndCount = 0, unassignedCount = 0, - trainingCount = 0; - for (var i = 0; i < office.employees.length; ++i) { - switch (office.employees[i].pos) { - case EmployeePositions.Operations: - ++opCount; break; - case EmployeePositions.Engineer: - ++engCount; break; - case EmployeePositions.Business: - ++busCount; break; - case EmployeePositions.Management: - ++mgmtCount; break; - case EmployeePositions.RandD: - ++rndCount; break; - case EmployeePositions.Unassigned: - ++unassignedCount; break; - case EmployeePositions.Training: - ++trainingCount; break; - default: - console.log("ERROR: Unrecognized employee position: " + office.employees[i].pos); - break; - } - } - - //Unassigned employee count display - industryEmployeeManagementUI.appendChild(createElement("p", { - display:"inline-block", - innerText:"Unassigned Employees: " + unassignedCount, - })); - industryEmployeeManagementUI.appendChild(createElement("br", {})); - - //General display of employee information (avg morale, avg energy, etc.) - industryEmployeeManagementUI.appendChild(industryEmployeeInfo); - industryEmployeeManagementUI.appendChild(createElement("br", {})); - - var positions = [EmployeePositions.Operations, EmployeePositions.Engineer, - EmployeePositions.Business, EmployeePositions.Management, - EmployeePositions.RandD, EmployeePositions.Training]; - var descriptions = ["Manages supply chain operations. Improves production.", //Operations - "Develops and maintains products and production systems. Improves production.", //Engineer - "Handles sales and finances. Improves sales.", //Business - "Leads and oversees employees and office operations. Improves production.", //Management - "Research new innovative ways to improve the company. Generates Scientific Research", //RandD - "Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations."] //Training - var counts = [opCount, engCount, busCount, mgmtCount, rndCount, trainingCount]; - for (var i = 0; i < positions.length; ++i) { - (function(corp, i) { - var info = createElement("h2", { - display:"inline-block", width:"50%", fontSize:"15px", - innerText: positions[i] + "(" + counts[i] + ")", - tooltip: descriptions[i] - }); - var plusBtn = createElement("a", { - class: unassignedCount > 0 ? "a-link-button" : "a-link-button-inactive", - display:"inline-block", innerText:"+", - clickListener:()=>{ - office.assignEmployeeToJob(positions[i]); - corp.displayDivisionContent(division, city); - } - }); - var minusBtn = createElement("a", { - class: counts[i] > 0 ? "a-link-button" : "a-link-button-inactive", - display:"inline-block", innerText:"-", - clickListener:()=>{ - office.unassignEmployeeFromJob(positions[i]); - corp.displayDivisionContent(division, city); - } - }); - var newline = createElement("br", {}); - industryEmployeeManagementUI.appendChild(info); - industryEmployeeManagementUI.appendChild(plusBtn); - industryEmployeeManagementUI.appendChild(minusBtn); - industryEmployeeManagementUI.appendChild(newline); - })(this, i); - } - } - industryEmployeePanel.appendChild(industryEmployeeManagementUI); - - //Warehouse Panel - var warehouse = division.warehouses[currentCityUi]; - if (warehouse instanceof Warehouse) { - warehouse.createUI({industry:division, company: this}); - } else { - industryWarehousePanel.appendChild(createElement("a", { - innerText:"Purchase Warehouse ($5b)", - class: "a-link-button", - clickListener:()=>{ - if (this.funds.lt(WarehouseInitialCost)) { - dialogBoxCreate("You do not have enough funds to do this!"); - } else { - division.warehouses[currentCityUi] = new Warehouse({ - loc:currentCityUi, - size:WarehouseInitialSize, - }); - this.funds = this.funds.minus(WarehouseInitialCost); - this.displayDivisionContent(division, currentCityUi); - } - return false; - } - })); - } - this.updateDivisionContent(division); -} - -Corporation.prototype.updateDivisionContent = function(division) { - if (!(division instanceof Industry)) { - console.log("ERROR: Invalid 'division' argument in Corporation.updateDivisionContent"); - return; - } - var vechain = (this.unlockUpgrades[4] === 1); - //Industry Overview Text - var profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber(), - profitStr = profit >= 0 ? numeralWrapper.format(profit, "$0.000a") : "-" + numeralWrapper.format(-1 * profit, "$0.000a"); - var advertisingInfo = ""; - if (vechain) { - var advertisingFactors = division.getAdvertisingFactors(); - var awarenessFac = advertisingFactors[1]; - var popularityFac = advertisingFactors[2]; - var ratioFac = advertisingFactors[3]; - var totalAdvertisingFac = advertisingFactors[0]; - advertisingInfo = - "

Advertising Multiplier: x" + formatNumber(totalAdvertisingFac, 3) + - "Total multiplier for this industry's sales due to its awareness and popularity
" + - "Awareness Bonus: x" + formatNumber(Math.pow(awarenessFac, 0.85), 3) + "
" + - "Popularity Bonus: x" + formatNumber(Math.pow(popularityFac, 0.85), 3) + "
" + - "Ratio Multiplier: x" + formatNumber(Math.pow(ratioFac, 0.85), 3) + "


" - - } - - removeChildrenFromElement(industryOverviewText); - industryOverviewText.appendChild(createElement("p", { - innerHTML:"Industry: " + division.type + " (Corp Funds: " + numeralWrapper.format(this.funds.toNumber(), "$0.000a") + ")

" + - "Awareness: " + formatNumber(division.awareness, 3) + "
" + - "Popularity: " + formatNumber(division.popularity, 3) + "
" + - advertisingInfo + "
" + - "Revenue: " + numeralWrapper.format(division.lastCycleRevenue.toNumber(), "$0.000a") + " / s
" + - "Expenses: " + numeralWrapper.format(division.lastCycleExpenses.toNumber(), "$0.000a") + " /s
" + - "Profit: " + profitStr + " / s

" - })); - industryOverviewText.appendChild(createElement("p", { - marginTop:"2px", - innerText:"Production Multiplier: " + formatNumber(division.prodMult, 2), - tooltip:"Production gain from owning production-boosting materials " + - "such as hardware, Robots, AI Cores, and Real Estate" - })); - industryOverviewText.appendChild(createElement("div", { - innerText:"?", class:"help-tip", - clickListener:()=>{ - dialogBoxCreate("Owning Hardware, Robots, AI Cores, and Real Estate " + - "can boost your Industry's production. The effect these " + - "materials have on your production varies between Industries. " + - "For example, Real Estate may be very effective for some Industries, " + - "but ineffective for others.

" + - "This division's production multiplier is calculated by summing " + - "the individual production multiplier of each of its office locations. " + - "This production multiplier is applied to each office. Therefore, it is " + - "beneficial to expand into new cities as this can greatly increase the " + - "production multiplier of your entire Division."); - } - })); - appendLineBreaks(industryOverviewText, 2); - industryOverviewText.appendChild(createElement("p", { - display:"inline-block", - innerText:"Scientific Research: " + formatNumber(division.sciResearch.qty, 3), - tooltip:"Scientific Research increases the quality of the materials and " + - "products that you produce." - })); - industryOverviewText.appendChild(createElement("div", { - class: "help-tip", - innerText: "Research", - clickListener: () => { - division.createResearchBox(); - } - })); - - //Office and Employee List - var office = division.offices[currentCityUi]; - industryEmployeeText.innerHTML = - "

Office Space


" + - "Size: " + office.employees.length + " / " + office.size + " employees"; - if (office.employees.length >= office.size) { - industryEmployeeHireButton.className = "a-link-button-inactive"; - industryEmployeeAutohireButton.className = "a-link-button-inactive tooltip"; - } else if (office.employees.length === 0) { - industryEmployeeHireButton.className = "a-link-button tooltip flashing-button"; - industryEmployeeAutohireButton.className = "a-link-button tooltip"; - } else { - industryEmployeeHireButton.className = "a-link-button"; - industryEmployeeAutohireButton.className = "a-link-button tooltip"; - } - - //Employee Overview stats - //Calculate average morale, happiness, and energy - var totalMorale = 0, totalHappiness = 0, totalEnergy = 0, totalSalary = 0, - avgMorale = 0, avgHappiness = 0, avgEnergy = 0; - for (let i = 0; i < office.employees.length; ++i) { - totalMorale += office.employees[i].mor; - totalHappiness += office.employees[i].hap; - totalEnergy += office.employees[i].ene; - totalSalary += office.employees[i].sal; - } - if (office.employees.length > 0) { - avgMorale = totalMorale / office.employees.length; - avgHappiness = totalHappiness / office.employees.length; - avgEnergy = totalEnergy / office.employees.length; - } - industryEmployeeInfo.innerHTML = - "Avg Employee Morale: " + formatNumber(avgMorale, 3) + "
" + - "Avg Employee Happiness: " + formatNumber(avgHappiness, 3) + "
" + - "Avg Employee Energy: " + formatNumber(avgEnergy, 3) + "
" + - "Total Employee Salary: " + numeralWrapper.format(totalSalary, "$0.000a"); - if (vechain) { //VeChain - Statistics - industryEmployeeInfo.appendChild(createElement("br", {})); - industryEmployeeInfo.appendChild(createElement("p", { - innerText:"Material Production: " + formatNumber(division.getOfficeProductivity(office), 3), - tooltip: "The base amount of material this office can produce. Does not include " + - "production multipliers from upgrades and materials. This value is based off " + - "the productivity of your Operations, Engineering, and Management employees" - })); - industryEmployeeInfo.appendChild(createElement("br", {})); - industryEmployeeInfo.appendChild(createElement("p", { - innerText:"Product Production: " + formatNumber(division.getOfficeProductivity(office, {forProduct:true}), 3), - tooltip: "The base amount of any given Product this office can produce. Does not include " + - "production multipliers from upgrades and materials. This value is based off " + - "the productivity of your Operations, Engineering, and Management employees" - })); - industryEmployeeInfo.appendChild(createElement("br", {})); - industryEmployeeInfo.appendChild(createElement("p", { - innerText: "Business Multiplier: x" + formatNumber(division.getBusinessFactor(office), 3), - tooltip: "The effect this office's 'Business' employees has on boosting sales" - })); - } - - //Warehouse - var warehouse = division.warehouses[currentCityUi]; - if (warehouse instanceof Warehouse) { - warehouse.updateUI({industry:division, company:this}); - } -} - -Corporation.prototype.createCityUITab = function(city, division) { - var tab = createElement("button", { - id:"cmpy-mgmt-city-" + city + "-tab", - class:"cmpy-mgmt-city-tab", - innerText:city, - clickListener:()=>{ - this.selectCityTab(tab, city); - this.displayDivisionContent(division, city); - return false; - } - }); - companyManagementPanel.appendChild(tab); -} - -Corporation.prototype.selectCityTab = function(activeTab, city) { - if (activeTab == null) { - activeTab = document.getElementById("cmpy-mgmt-city-" + city + "-tab"); - if (activeTab == null) {return;} - } - for (var i = 0; i < cityTabs.length; ++i) { - cityTabs[i].className = "cmpy-mgmt-city-tab"; - } - activeTab.className = "cmpy-mgmt-city-tab current"; -} - -Corporation.prototype.clearUI = function() { - //Delete everything - if (companyManagementDiv != null) {removeElementById(companyManagementDiv.id);} - - //Reset global DOM variables - companyManagementDiv = null; - companyManagementPanel = null; - currentCityUi = null; - - corporationUnlockUpgrades = null; - corporationUpgrades = null; - - sellSharesButton = null; - issueNewSharesButton = null; - sellSharesButtonTooltip = null; - issueNewSharesButtonTooltip = null; - - industryOverviewPanel = null; - industryOverviewText = null; - - industryEmployeePanel = null; - industryEmployeeText = null; - industryEmployeeHireButton = null; - industryEmployeeAutohireButton = null; - industryEmployeeManagementUI = null; - industryEmployeeInfo = null; - industryIndividualEmployeeInfo = null; - - industryOfficeUpgradeSizeButton = null; - - industryWarehousePanel = null; - industrySmartSupplyCheckbox = null; - industryWarehouseStorageText = null; - industryWarehouseUpgradeSizeButton = null; - industryWarehouseStateText = null; - industryWarehouseMaterials = null; - industryWarehouseProducts = null; - - researchTreeBoxOpened = false; - researchTreeBox = null; - - companyManagementHeaderTabs = null; - headerTabs = null; - cityTabs = null; - - document.getElementById("character-overview-wrapper").style.visibility = "visible"; -} - -Corporation.prototype.toJSON = function() { - return Generic_toJSON("Corporation", this); -} - -Corporation.fromJSON = function(value) { - return Generic_fromJSON(Corporation, value.data); -} - -Reviver.constructors.Corporation = Corporation; - -export {Corporation}; diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx new file mode 100644 index 000000000..3ec1511a4 --- /dev/null +++ b/src/Corporation/Corporation.jsx @@ -0,0 +1,2343 @@ +import { AllCorporationStates, + CorporationState } from "./CorporationState"; +import { CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades"; +import { CorporationUpgrades } from "./data/CorporationUpgrades"; +import { EmployeePositions } from "./EmployeePositions"; +import { Industries, + IndustryStartingCosts, + IndustryDescriptions, + IndustryResearchTrees } from "./IndustryData"; +import { IndustryUpgrades } from "./IndustryUpgrades"; +import { Material } from "./Material"; +import { MaterialSizes } from "./MaterialSizes"; +import { Product } from "./Product"; +import { ResearchMap } from "./ResearchMap"; +import { Warehouse } from "./Warehouse"; + +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import { CONSTANTS } from "../Constants"; +import { Factions } from "../Faction/Factions"; +import { showLiterature } from "../Literature"; +import { Locations } from "../Locations"; +import { Player } from "../Player"; + +import { numeralWrapper } from "../ui/numeralFormat"; +import { Page, routing } from "../ui/navigationTracking"; + +import { dialogBoxCreate } from "../../utils/DialogBox"; +import { clearSelector } from "../../utils/uiHelpers/clearSelector"; +import { Reviver, + Generic_toJSON, + Generic_fromJSON } from "../../utils/JSONReviver"; +import { appendLineBreaks } from "../../utils/uiHelpers/appendLineBreaks"; +import { createElement } from "../../utils/uiHelpers/createElement"; +import { createPopup } from "../../utils/uiHelpers/createPopup"; +import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton"; +import { formatNumber, generateRandomString } from "../../utils/StringHelperFunctions"; +import { getRandomInt } from "../../utils/helpers/getRandomInt"; +import { isString } from "../../utils/helpers/isString"; +import { KEY } from "../../utils/helpers/keyCodes"; +import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; +import { removeElement } from "../../utils/uiHelpers/removeElement"; +import { removeElementById } from "../../utils/uiHelpers/removeElementById"; +import { yesNoBoxCreate, + yesNoTxtInpBoxCreate, + yesNoBoxGetYesButton, + yesNoBoxGetNoButton, + yesNoTxtInpBoxGetYesButton, + yesNoTxtInpBoxGetNoButton, + yesNoTxtInpBoxGetInput, + yesNoBoxClose, + yesNoTxtInpBoxClose, + yesNoBoxOpen } from "../../utils/YesNoBox"; + +// UI Related Imports +import React from "react"; +import ReactDOM from "react-dom"; +import { CorporationEventHandler } from "./ui/CorporationUIEventHandler"; +import { CorporationRoot } from "./ui/Root"; +import { CorporationRouting } from "./ui/Routing"; + +import Decimal from "decimal.js"; + + +/* Constants */ +export const INITIALSHARES = 1e9; //Total number of shares you have at your company +export const SHARESPERPRICEUPDATE = 1e6; //When selling large number of shares, price is dynamically updated for every batch of this amount +export const IssueNewSharesCooldown = 216e3; // 12 Hour in terms of game cycles +export const SellSharesCooldown = 18e3; // 1 Hour in terms of game cycles + +export const CyclesPerMarketCycle = 50; +export const CyclesPerIndustryStateCycle = CyclesPerMarketCycle / AllCorporationStates.length; +export const SecsPerMarketCycle = CyclesPerMarketCycle / 5; + +export const Cities = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"]; + +export const WarehouseInitialCost = 5e9; //Initial purchase cost of warehouse +export const WarehouseInitialSize = 100; +export const WarehouseUpgradeBaseCost = 1e9; + +export const OfficeInitialCost = 4e9; +export const OfficeInitialSize = 3; +export const OfficeUpgradeBaseCost = 1e9; + +export const BribeThreshold = 100e12; //Money needed to be able to bribe for faction rep +export const BribeToRepRatio = 1e9; //Bribe Value divided by this = rep gain + +export const ProductProductionCostRatio = 5; //Ratio of material cost of a product to its production cost + +export const DividendMaxPercentage = 50; + +export const EmployeeSalaryMultiplier = 3; // Employee stats multiplied by this to determine initial salary +export const CyclesPerEmployeeRaise = 400; // All employees get a raise every X market cycles +export const EmployeeRaiseAmount = 50; // Employee salary increases by this (additive) + +export const BaseMaxProducts = 3; // Initial value for maximum number of products allowed + +// Delete Research Popup Box when clicking outside of it +let researchTreeBoxOpened = false; +let researchTreeBox = null; +$(document).mousedown(function(event) { + const boxId = "corporation-research-popup-box"; + const contentId = "corporation-research-popup-box-content"; + if (researchTreeBoxOpened) { + if ( $(event.target).closest("#" + contentId).get(0) == null ) { + // Delete the box + removeElement(researchTreeBox); + researchTreeBox = null; + researchTreeBoxOpened = false; + } + } +}); + +var empManualAssignmentModeActive = false; +function Industry(params={}) { + this.offices = { //Maps locations to offices. 0 if no office at that location + [Locations.Aevum]: 0, + [Locations.Chongqing]: 0, + [Locations.Sector12]: new OfficeSpace({ + loc:Locations.Sector12, + size:OfficeInitialSize, + }), + [Locations.NewTokyo]: 0, + [Locations.Ishima]: 0, + [Locations.Volhaven]: 0 + }; + + this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location + [Locations.Aevum]: 0, + [Locations.Chonqing]: 0, + [Locations.Sector12]: new Warehouse({ + loc:Locations.Sector12, + size: WarehouseInitialSize, + }), + [Locations.NewTokyo]: 0, + [Locations.Ishima]: 0, + [Locations.Volhaven]: 0 + }; + + this.name = params.name ? params.name : 0; + this.type = params.type ? params.type : 0; + + this.sciResearch = new Material({name: "Scientific Research"}); + this.researched = {}; // Object of acquired Research. Keys = research name + + //A map of the NAME of materials required to create produced materials to + //how many are needed to produce 1 unit of produced materials + this.reqMats = {}; + + //An array of the name of materials being produced + this.prodMats = []; + + this.products = {}; + this.makesProducts = false; + + this.awareness = 0; + this.popularity = 0; //Should always be less than awareness + this.startingCost = 0; + + /* The following are factors for how much production/other things are increased by + different factors. The production increase always has diminishing returns, + and they are all reprsented by exponentials of < 1 (e.g x ^ 0.5, x ^ 0.8) + The number for these represent the exponential. A lower number means more + diminishing returns */ + this.reFac = 0; //Real estate Factor + this.sciFac = 0; //Scientific Research Factor, affects quality + this.hwFac = 0; //Hardware factor + this.robFac = 0; //Robotics Factor + this.aiFac = 0; //AI Cores factor; + this.advFac = 0; //Advertising factor, affects sales + + this.prodMult = 0; //Production multiplier + + //Financials + this.lastCycleRevenue = new Decimal(0); + this.lastCycleExpenses = new Decimal(0); + this.thisCycleRevenue = new Decimal(0); + this.thisCycleExpenses = new Decimal(0); + + //Upgrades + var numUpgrades = Object.keys(IndustryUpgrades).length; + this.upgrades = Array(numUpgrades).fill(0); + + this.state = "START"; + this.newInd = true; + + this.init(); +} + +Industry.prototype.init = function() { + //Set the unique properties of an industry (how much its affected by real estate/scientific research, etc.) + this.startingCost = IndustryStartingCosts[this.type]; + switch (this.type) { + case Industries.Energy: + this.reFac = 0.65; + this.sciFac = 0.7; + this.robFac = 0.05; + this.aiFac = 0.3; + this.advFac = 0.08; + this.reqMats = { + "Hardware": 0.1, + "Metal": 0.2, + }; + this.prodMats = ["Energy"]; + break; + case Industries.Utilities: + case "Utilities": + this.reFac = 0.5; + this.sciFac = 0.6; + this.robFac = 0.4; + this.aiFac = 0.4; + this.advFac = 0.08; + this.reqMats = { + "Hardware": 0.1, + "Metal": 0.1, + } + this.prodMats = ["Water"]; + break; + case Industries.Agriculture: + this.reFac = 0.72; + this.sciFac = 0.5; + this.hwFac = 0.2; + this.robFac = 0.3; + this.aiFac = 0.3; + this.advFac = 0.04; + this.reqMats = { + "Water": 0.5, + "Energy": 0.5, + } + this.prodMats = ["Plants", "Food"]; + break; + case Industries.Fishing: + this.reFac = 0.15; + this.sciFac = 0.35; + this.hwFac = 0.35; + this.robFac = 0.5; + this.aiFac = 0.2; + this.advFac = 0.08; + this.reqMats = { + "Energy": 0.5, + } + this.prodMats = ["Food"]; + break; + case Industries.Mining: + this.reFac = 0.3; + this.sciFac = 0.26; + this.hwFac = 0.4; + this.robFac = 0.45; + this.aiFac = 0.45; + this.advFac = 0.06; + this.reqMats = { + "Energy": 0.8, + } + this.prodMats = ["Metal"]; + break; + case Industries.Food: + //reFac is unique for this bc it diminishes greatly per city. Handle this separately in code? + this.sciFac = 0.12; + this.hwFac = 0.15; + this.robFac = 0.3; + this.aiFac = 0.25; + this.advFac = 0.25; + this.reFac = 0.05; + this.reqMats = { + "Food": 0.5, + "Water": 0.5, + "Energy": 0.2, + } + this.makesProducts = true; + break; + case Industries.Tobacco: + this.reFac = 0.15; + this.sciFac = 0.75; + this.hwFac = 0.15; + this.robFac = 0.2; + this.aiFac = 0.15; + this.advFac = 0.2; + this.reqMats = { + "Plants": 1, + "Water": 0.2, + } + this.makesProducts = true; + break; + case Industries.Chemical: + this.reFac = 0.25; + this.sciFac = 0.75; + this.hwFac = 0.2; + this.robFac = 0.25; + this.aiFac = 0.2; + this.advFac = 0.07; + this.reqMats = { + "Plants": 1, + "Energy": 0.5, + "Water": 0.5, + } + this.prodMats = ["Chemicals"]; + break; + case Industries.Pharmaceutical: + this.reFac = 0.05; + this.sciFac = 0.8; + this.hwFac = 0.15; + this.robFac = 0.25; + this.aiFac = 0.2; + this.advFac = 0.16; + this.reqMats = { + "Chemicals": 2, + "Energy": 1, + "Water": 0.5, + } + this.prodMats = ["Drugs"]; + this.makesProducts = true; + break; + case Industries.Computer: + case "Computer": + this.reFac = 0.2; + this.sciFac = 0.62; + this.robFac = 0.36; + this.aiFac = 0.19; + this.advFac = 0.17; + this.reqMats = { + "Metal": 2, + "Energy": 1, + } + this.prodMats = ["Hardware"]; + this.makesProducts = true; + break; + case Industries.Robotics: + this.reFac = 0.32; + this.sciFac = 0.65; + this.aiFac = 0.36; + this.advFac = 0.18; + this.hwFac = 0.19; + this.reqMats = { + "Hardware": 5, + "Energy": 3, + } + this.prodMats = ["Robots"]; + this.makesProducts = true; + break; + case Industries.Software: + this.sciFac = 0.62; + this.advFac = 0.16; + this.hwFac = 0.25; + this.reFac = 0.1; + this.aiFac = 0.15; + this.robFac = 0.05; + this.reqMats = { + "Hardware": 0.5, + "Energy": 0.5, + } + this.prodMats = ["AICores"]; + this.makesProducts = true; + break; + case Industries.Healthcare: + this.reFac = 0.1; + this.sciFac = 0.75; + this.advFac = 0.11; + this.hwFac = 0.1; + this.robFac = 0.1; + this.aiFac = 0.1; + this.reqMats = { + "Robots": 10, + "AICores": 5, + "Energy": 5, + "Water": 5, + } + this.makesProducts = true; + break; + case Industries.RealEstate: + this.robFac = 0.6; + this.aiFac = 0.6; + this.advFac = 0.25; + this.sciFac = 0.05; + this.hwFac = 0.05; + this.reqMats = { + "Metal": 5, + "Energy": 5, + "Water": 2, + "Hardware": 4 + } + this.prodMats = ["RealEstate"]; + this.makesProducts = true; + break; + default: + console.log("ERR: Invalid Industry Type passed into Industry.init(): " + this.type); + return; + } +} + +Industry.prototype.getProductDescriptionText = function() { + if (!this.makesProducts) {return;} + switch (this.type) { + case Industries.Food: + return "create and manage restaurants"; + case Industries.Tobacco: + return "create tobacco and tobacco-related products"; + case Industries.Pharmaceutical: + return "develop new pharmaceutical drugs"; + case Industries.Computer: + case "Computer": + return "create new computer hardware and networking infrastructures"; + case Industries.Robotics: + return "build specialized robots and robot-related products"; + case Industries.Software: + return "develop computer software"; + case Industries.Healthcare: + return "build and manage hospitals"; + case Industries.RealEstate: + return "develop and manage real estate properties"; + default: + console.log("ERROR: Invalid industry type in Industry.getProductDescriptionText"); + return ""; + } +} + +Industry.prototype.getMaximumNumberProducts = function() { + if (!this.makesProducts) { return 0; } + + // Calculate additional number of allowed Products from Research/Upgrades + let additional = 0; + if (this.hasResearch("uPgrade: Capacity.I")) { ++additional; } + if (this.hasResearch("uPgrade: Capacity.II")) { ++additional; } + + return BaseMaxProducts + additional; +} + +Industry.prototype.hasMaximumNumberProducts = function() { + return (Object.keys(this.products).length >= this.getMaximumNumberProducts()); +} + +//Calculates the values that factor into the production and properties of +//materials/products (such as quality, etc.) +Industry.prototype.calculateProductionFactors = function() { + var multSum = 0; + for (var i = 0; i < Cities.length; ++i) { + var city = Cities[i]; + var warehouse = this.warehouses[city]; + if (!(warehouse instanceof Warehouse)) { + continue; + } + + var materials = warehouse.materials, + office = this.offices[city]; + + var cityMult = Math.pow(0.002 * materials.RealEstate.qty+1, this.reFac) * + Math.pow(0.002 * materials.Hardware.qty+1, this.hwFac) * + Math.pow(0.002 * materials.Robots.qty+1, this.robFac) * + Math.pow(0.002 * materials.AICores.qty+1, this.aiFac); + multSum += Math.pow(cityMult, 0.73); + } + + multSum < 1 ? this.prodMult = 1 : this.prodMult = multSum; +} + +Industry.prototype.updateWarehouseSizeUsed = function(warehouse) { + if (warehouse instanceof Warehouse) { + //This resets the size back to 0 and then accounts for materials + warehouse.updateMaterialSizeUsed(); + } + + for (var prodName in this.products) { + if (this.products.hasOwnProperty(prodName)) { + var prod = this.products[prodName]; + warehouse.sizeUsed += (prod.data[warehouse.loc][0] * prod.siz); + if (prod.data[warehouse.loc][0] > 0) { + warehouse.breakdown += (prodName + ": " + formatNumber(prod.data[warehouse.loc][0] * prod.siz, 0) + "
"); + } + } + } +} + +Industry.prototype.process = function(marketCycles=1, state, company) { + this.state = state; + + //At the start of a cycle, store and reset revenue/expenses + //Then calculate salaries and processs the markets + if (state === "START") { + if (isNaN(this.thisCycleRevenue) || isNaN(this.thisCycleExpenses)) { + console.log("ERROR: NaN in Corporation's computed revenue/expenses"); + console.log(this.thisCycleRevenue.toString()); + console.log(this.thisCycleExpenses.toString()); + dialogBoxCreate("Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer"); + this.thisCycleRevenue = new Decimal(0); + this.thisCycleExpenses = new Decimal(0); + } + this.lastCycleRevenue = this.thisCycleRevenue.dividedBy(marketCycles * SecsPerMarketCycle); + this.lastCycleExpenses = this.thisCycleExpenses.dividedBy(marketCycles * SecsPerMarketCycle); + this.thisCycleRevenue = new Decimal(0); + this.thisCycleExpenses = new Decimal(0); + + //Once you start making revenue, the player should no longer be + //considered new, and therefore no longer needs the 'tutorial' UI elements + if (this.lastCycleRevenue.gt(0)) {this.newInd = false;} + + //Process offices (and the employees in them) + var employeeSalary = 0; + for (var officeLoc in this.offices) { + if (this.offices[officeLoc] instanceof OfficeSpace) { + employeeSalary += this.offices[officeLoc].process(marketCycles, {industry:this, corporation:company}); + } + } + this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary); + + //Process change in demand/competition of materials/products + this.processMaterialMarket(marketCycles); + this.processProductMarket(marketCycles); + + //Process loss of popularity + this.popularity -= (marketCycles * .0001); + this.popularity = Math.max(0, this.popularity); + + //Process Dreamsense gains + var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4; + if (popularityGain > 0) { + this.popularity += (popularityGain * marketCycles); + this.awareness += (awarenessGain * marketCycles); + } + + return; + } + + //Process production, purchase, and import/export of materials + var res = this.processMaterials(marketCycles, company); + this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); + this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); + + //Process creation, production & sale of products + res = this.processProducts(marketCycles, company); + this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); + this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); + +} + +//Process change in demand and competition for this industry's materials +Industry.prototype.processMaterialMarket = function(marketCycles=1) { + //References to prodMats and reqMats + var reqMats = this.reqMats, prodMats = this.prodMats; + + //Only 'process the market' for materials that this industry deals with + for (var i = 0; i < Cities.length; ++i) { + //If this industry has a warehouse in this city, process the market + //for every material this industry requires or produces + if (this.warehouses[Cities[i]] instanceof Warehouse) { + var wh = this.warehouses[Cities[i]]; + for (var name in reqMats) { + if (reqMats.hasOwnProperty(name)) { + wh.materials[name].processMarket(); + } + } + + //Produced materials are stored in an array + for (var foo = 0; foo < prodMats.length; ++foo) { + wh.materials[prodMats[foo]].processMarket(); + } + + //Process these twice because these boost production + wh.materials["Hardware"].processMarket(); + wh.materials["Robots"].processMarket(); + wh.materials["AICores"].processMarket(); + wh.materials["RealEstate"].processMarket(); + } + } +} + +//Process change in demand and competition for this industry's products +Industry.prototype.processProductMarket = function(marketCycles=1) { + //Demand gradually decreases, and competition gradually increases + for (var name in this.products) { + if (this.products.hasOwnProperty(name)) { + var product = this.products[name]; + var change = getRandomInt(1, 3) * 0.0004; + if (this.type === Industries.Pharmaceutical || this.type === Industries.Software || + this.type === Industries.Robotics) { + change *= 3; + } + change *= marketCycles; + product.dmd -= change; + product.cmp += change; + product.cmp = Math.min(product.cmp, 99.99); + product.dmd = Math.max(product.dmd, 0.001); + } + } +} + +//Process production, purchase, and import/export of materials +Industry.prototype.processMaterials = function(marketCycles=1, company) { + var revenue = 0, expenses = 0, industry = this; + this.calculateProductionFactors(); + + //At the start of the export state, set the imports of everything to 0 + if (this.state === "EXPORT") { + for (var i = 0; i < Cities.length; ++i) { + var city = Cities[i], office = this.offices[city]; + if (!(this.warehouses[city] instanceof Warehouse)) { + continue; + } + var warehouse = this.warehouses[city]; + for (var matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + var mat = warehouse.materials[matName]; + mat.imp = 0; + } + } + } + } + + for (var i = 0; i < Cities.length; ++i) { + var city = Cities[i], office = this.offices[city]; + + if (this.warehouses[city] instanceof Warehouse) { + var warehouse = this.warehouses[city]; + + switch(this.state) { + + case "PURCHASE": + /* Process purchase of materials */ + for (var matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + (function(matName, ind) { + var mat = warehouse.materials[matName]; + var buyAmt, maxAmt; + if (warehouse.smartSupplyEnabled && Object.keys(ind.reqMats).includes(matName)) { + //Smart supply tracker is stored as per second rate + mat.buy = ind.reqMats[matName] * warehouse.smartSupplyStore; + buyAmt = mat.buy * SecsPerMarketCycle * marketCycles; + } else { + buyAmt = (mat.buy * SecsPerMarketCycle * marketCycles); + } + + if (matName == "RealEstate") { + maxAmt = buyAmt; + } else { + maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]); + } + var buyAmt = Math.min(buyAmt, maxAmt); + if (buyAmt > 0) { + mat.qty += buyAmt; + expenses += (buyAmt * mat.bCost); + } + })(matName, industry); + this.updateWarehouseSizeUsed(warehouse); + } + } //End process purchase of materials + break; + + case "PRODUCTION": + warehouse.smartSupplyStore = 0; //Reset smart supply amount + + /* Process production of materials */ + if (this.prodMats.length > 0) { + var mat = warehouse.materials[this.prodMats[0]]; + //Calculate the maximum production of this material based + //on the office's productivity + var maxProd = this.getOfficeProductivity(office) + * this.prodMult // Multiplier from materials + * company.getProductionMultiplier() + * this.getProductionMultiplier(); // Multiplier from Research + let prod; + + if (mat.prdman[0]) { + //Production is manually limited + prod = Math.min(maxProd, mat.prdman[1]); + } else { + prod = maxProd; + } + prod *= (SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle + //Calculate net change in warehouse storage making + //the produced materials will cost + var totalMatSize = 0; + for (var tmp = 0; tmp < this.prodMats.length; ++tmp) { + totalMatSize += (MaterialSizes[this.prodMats[tmp]]); + } + for (var reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + var normQty = this.reqMats[reqMatName]; + totalMatSize -= (MaterialSizes[reqMatName] * normQty); + } + } + //If not enough space in warehouse, limit the amount of produced materials + if (totalMatSize > 0) { + var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize); + prod = Math.min(maxAmt, prod); + } + + if (prod < 0) {prod = 0;} + + //Keep track of production for smart supply (/s) + warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles)); + + //Make sure we have enough resource to make our materials + var producableFrac = 1; + for (var reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + var req = this.reqMats[reqMatName] * prod; + if (warehouse.materials[reqMatName].qty < req) { + producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); + } + } + } + if (producableFrac <= 0) {producableFrac = 0; prod = 0;} + + //Make our materials if they are producable + if (producableFrac > 0 && prod > 0) { + for (var reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac); + warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; + warehouse.materials[reqMatName].prd = 0; + warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles); + } + } + for (var j = 0; j < this.prodMats.length; ++j) { + warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac); + warehouse.materials[this.prodMats[j]].qlt = + (office.employeeProd[EmployeePositions.Engineer] / 100 + + Math.pow(this.sciResearch.qty, this.sciFac) + + Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3); + } + } else { + for (var reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + warehouse.materials[reqMatName].prd = 0; + } + } + } + + //Per second + var fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles); + for (var fooI = 0; fooI < this.prodMats.length; ++fooI) { + warehouse.materials[this.prodMats[fooI]].prd = fooProd; + } + } else { + //If this doesn't produce any materials, then it only creates + //Products. Creating products will consume materials. The + //Production of all consumed materials must be set to 0 + for (var reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + warehouse.materials[reqMatName].prd = 0; + } + } + } + break; + + case "SALE": + /* Process sale of materials */ + for (var matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + var mat = warehouse.materials[matName]; + if (mat.sCost < 0 || mat.sllman[0] === false) { + mat.sll = 0; + continue; + } + var mat = warehouse.materials[matName]; + + var sCost; + if (isString(mat.sCost)) { + sCost = mat.sCost.replace(/MP/g, mat.bCost); + sCost = eval(sCost); + } else { + sCost = mat.sCost; + } + + //Calculate how much of the material sells (per second) + let markup = 1, markupLimit = mat.getMarkupLimit(); + if (sCost > mat.bCost) { + //Penalty if difference between sCost and bCost is greater than markup limit + if ((sCost - mat.bCost) > markupLimit) { + markup = Math.pow(markupLimit / (sCost - mat.bCost), 2); + } + } else if (sCost < mat.bCost) { + if (sCost <= 0) { + markup = 1e12; //Sell everything, essentially discard + } else { + //Lower prices than market increases sales + markup = mat.bCost / sCost; + } + } + var businessFactor = this.getBusinessFactor(office); //Business employee productivity + var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity + var marketFactor = this.getMarketFactor(mat); //Competition + demand + var maxSell = (mat.qlt + .001) + * marketFactor + * markup + * businessFactor + * company.getSalesMultiplier() + * advertisingFactor + * this.getSalesMultiplier(); + var sellAmt; + if (isString(mat.sllman[1])) { + //Dynamically evaluated + var tmp = mat.sllman[1].replace(/MAX/g, maxSell); + tmp = tmp.replace(/PROD/g, mat.prd); + try { + sellAmt = eval(tmp); + } catch(e) { + dialogBoxCreate("Error evaluating your sell amount for material " + mat.name + + " in " + this.name + "'s " + city + " office. The sell amount " + + "is being set to zero"); + sellAmt = 0; + } + sellAmt = Math.min(maxSell, sellAmt); + } else if (mat.sllman[1] === -1) { + //Backwards compatibility, -1 = MAX + sellAmt = maxSell; + } else { + //Player's input value is just a number + sellAmt = Math.min(maxSell, mat.sllman[1]); + } + + sellAmt = (sellAmt * SecsPerMarketCycle * marketCycles); + sellAmt = Math.min(mat.qty, sellAmt); + if (sellAmt < 0) { + console.log("sellAmt calculated to be negative"); + mat.sll = 0; + continue; + } + if (sellAmt && sCost >= 0) { + mat.qty -= sellAmt; + revenue += (sellAmt * sCost); + mat.sll = sellAmt / (SecsPerMarketCycle * marketCycles); + } else { + mat.sll = 0; + } + } + } //End processing of sale of materials + break; + + case "EXPORT": + for (var matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + var mat = warehouse.materials[matName]; + mat.totalExp = 0; //Reset export + for (var expI = 0; expI < mat.exp.length; ++expI) { + var exp = mat.exp[expI]; + var amt = exp.amt.replace(/MAX/g, mat.qty / (SecsPerMarketCycle * marketCycles)); + try { + amt = eval(amt); + } catch(e) { + dialogBoxCreate("Calculating export for " + mat.name + " in " + + this.name + "'s " + city + " division failed with " + + "error: " + e); + continue; + } + if (isNaN(amt)) { + dialogBoxCreate("Error calculating export amount for " + mat.name + " in " + + this.name + "'s " + city + " division."); + continue; + } + amt = amt * SecsPerMarketCycle * marketCycles; + + if (mat.qty < amt) { + amt = mat.qty; + } + if (amt === 0) { + break; //None left + } + for (var foo = 0; foo < company.divisions.length; ++foo) { + if (company.divisions[foo].name === exp.ind) { + var expIndustry = company.divisions[foo]; + var expWarehouse = expIndustry.warehouses[exp.city]; + if (!(expWarehouse instanceof Warehouse)) { + console.log("ERROR: Invalid export! " + expIndustry.name + " " + exp.city); + break; + } + + //Make sure theres enough space in warehouse + if (expWarehouse.sizeUsed >= expWarehouse.size) { + return; //Warehouse at capacity + } else { + var maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]); + amt = Math.min(maxAmt, amt); + } + expWarehouse.materials[matName].imp += (amt / (SecsPerMarketCycle * marketCycles)); + expWarehouse.materials[matName].qty += amt; + expWarehouse.materials[matName].qlt = mat.qlt; + mat.qty -= amt; + mat.totalExp += amt; + expIndustry.updateWarehouseSizeUsed(expWarehouse); + break; + } + } + } + //totalExp should be per second + mat.totalExp /= (SecsPerMarketCycle * marketCycles); + } + } + + break; + + case "START": + break; + default: + console.log("ERROR: Invalid state: " + this.state); + break; + } //End switch(this.state) + this.updateWarehouseSizeUsed(warehouse); + + } // End warehouse + + //Produce Scientific Research based on R&D employees + //Scientific Research can be produced without a warehouse + if (office instanceof OfficeSpace) { + this.sciResearch.qty += (.005 + * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.55) + * company.getScientificResearchMultiplier() + * this.getScientificResearchMultiplier()); + } + } + return [revenue, expenses]; +} + +//Process production & sale of this industry's FINISHED products (including all of their stats) +Industry.prototype.processProducts = function(marketCycles=1, corporation) { + var revenue = 0, expenses = 0; + + //Create products + if (this.state === "PRODUCTION") { + for (var prodName in this.products) { + if (this.products.hasOwnProperty(prodName)) { + var prod = this.products[prodName]; + if (!prod.fin) { + var city = prod.createCity, office = this.offices[city]; + var total = office.employeeProd[EmployeePositions.Operations] + + office.employeeProd[EmployeePositions.Engineer] + + office.employeeProd[EmployeePositions.Management], ratio; + if (total === 0) { + ratio = 0; + } else { + ratio = office.employeeProd[EmployeePositions.Engineer] / total + + office.employeeProd[EmployeePositions.Operations] / total + + office.employeeProd[EmployeePositions.Management] / total; + } + prod.createProduct(marketCycles, ratio * Math.pow(total, 0.35)); + if (prod.prog >= 100) { + prod.finishProduct(office.employeeProd, this); + } + break; + } + } + } + } + + //Produce Products + for (var prodName in this.products) { + if (this.products.hasOwnProperty(prodName)) { + var prod = this.products[prodName]; + if (prod instanceof Product && prod.fin) { + revenue += this.processProduct(marketCycles, prod, corporation); + } + } + } + return [revenue, expenses]; +} + +//Processes FINISHED products +Industry.prototype.processProduct = function(marketCycles=1, product, corporation) { + var totalProfit = 0; + for (var i = 0; i < Cities.length; ++i) { + var city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city]; + if (warehouse instanceof Warehouse) { + switch(this.state) { + + case "PRODUCTION": + //Calculate the maximum production of this material based + //on the office's productivity + var maxProd = this.getOfficeProductivity(office, {forProduct:true}) + * corporation.getProductionMultiplier() + * this.prodMult // Multiplier from materials + * this.getProductionMultiplier() // Multiplier from research + * this.getProductProductionMultiplier(); // Multiplier from research + let prod; + + //Account for whether production is manually limited + if (product.prdman[city][0]) { + prod = Math.min(maxProd, product.prdman[city][1]); + } else { + prod = maxProd; + } + prod *= (SecsPerMarketCycle * marketCycles); + + //Calculate net change in warehouse storage making the Products will cost + var netStorageSize = product.siz; + for (var reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + var normQty = product.reqMats[reqMatName]; + netStorageSize -= (MaterialSizes[reqMatName] * normQty); + } + } + + //If there's not enough space in warehouse, limit the amount of Product + if (netStorageSize > 0) { + var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / netStorageSize); + prod = Math.min(maxAmt, prod); + } + + warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles)); + + //Make sure we have enough resources to make our Products + var producableFrac = 1; + for (var reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + var req = product.reqMats[reqMatName] * prod; + if (warehouse.materials[reqMatName].qty < req) { + producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); + } + } + } + + //Make our Products if they are producable + if (producableFrac > 0 && prod > 0) { + for (var reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + var reqMatQtyNeeded = (product.reqMats[reqMatName] * prod * producableFrac); + warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; + warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles); + } + } + //Quantity + product.data[city][0] += (prod * producableFrac); + } + + //Keep track of production Per second + product.data[city][1] = prod * producableFrac / (SecsPerMarketCycle * marketCycles); + break; + + case "SALE": + //Process sale of Products + product.pCost = 0; //Estimated production cost + for (var reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + product.pCost += (product.reqMats[reqMatName] * warehouse.materials[reqMatName].bCost); + } + } + + //Since its a product, its production cost is increased for labor + product.pCost *= ProductProductionCostRatio; + + //Calculate Sale Cost (sCost), which could be dynamically evaluated + var sCost; + if (isString(product.sCost)) { + sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku); + sCost = eval(sCost); + } else { + sCost = product.sCost; + } + + var markup = 1, markupLimit = product.rat / product.mku; + if (sCost > product.pCost) { + if ((sCost - product.pCost) > markupLimit) { + markup = markupLimit / (sCost - product.pCost); + } + } + var businessFactor = this.getBusinessFactor(office); //Business employee productivity + var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity + var marketFactor = this.getMarketFactor(product); //Competition + demand + var maxSell = 0.5 + * Math.pow(product.rat, 0.65) + * marketFactor + * corporation.getSalesMultiplier() + * Math.pow(markup, 2) + * businessFactor + * advertisingFactor + * this.getSalesMultiplier(); + var sellAmt; + if (product.sllman[city][0] && isString(product.sllman[city][1])) { + //Sell amount is dynamically evaluated + var tmp = product.sllman[city][1].replace(/MAX/g, maxSell); + tmp = tmp.replace(/PROD/g, product.data[city][1]); + try { + tmp = eval(tmp); + } catch(e) { + dialogBoxCreate("Error evaluating your sell price expression for " + product.name + + " in " + this.name + "'s " + city + " office. Sell price is being set to MAX"); + tmp = maxSell; + } + sellAmt = Math.min(maxSell, tmp); + } else if (product.sllman[city][0] && product.sllman[city][1] > 0) { + //Sell amount is manually limited + sellAmt = Math.min(maxSell, product.sllman[city][1]); + } else { + //Backwards compatibility, -1 = 0 + sellAmt = maxSell; + } + if (sellAmt < 0) { sellAmt = 0; } + sellAmt = sellAmt * SecsPerMarketCycle * marketCycles; + sellAmt = Math.min(product.data[city][0], sellAmt); //data[0] is qty + if (sellAmt && sCost) { + product.data[city][0] -= sellAmt; //data[0] is qty + totalProfit += (sellAmt * sCost); + product.data[city][2] = sellAmt / (SecsPerMarketCycle * marketCycles); //data[2] is sell property + } else { + product.data[city][2] = 0; //data[2] is sell property + } + break; + + case "START": + case "PURCHASE": + case "EXPORT": + break; + default: + console.log("ERROR: Invalid State: " + this.state); + break; + } //End switch(this.state) + } + } + return totalProfit; +} + +Industry.prototype.discontinueProduct = function(product, parentRefs) { + var company = parentRefs.company, industry = parentRefs.industry; + for (var productName in this.products) { + if (this.products.hasOwnProperty(productName)) { + if (product === this.products[productName]) { + delete this.products[productName]; + company.updateUIContent(); + } + } + } +} + +Industry.prototype.upgrade = function(upgrade, refs) { + var corporation = refs.corporation, division = refs.division, + office = refs.office; + var upgN = upgrade[0], basePrice = upgrade[1], priceMult = upgrade[2], + upgradeBenefit = upgrade[3]; + while (this.upgrades.length <= upgN) {this.upgrades.push(0);} + ++this.upgrades[upgN]; + + switch (upgN) { + case 0: //Coffee, 5% energy per employee + for (let i = 0; i < office.employees.length; ++i) { + office.employees[i].ene = Math.min(office.employees[i].ene * 1.05, office.maxEne); + } + break; + case 1: //AdVert.Inc, + var advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier(); + this.awareness += (3 * advMult); + this.popularity += (1 * advMult); + this.awareness *= (1.01 * advMult); + this.popularity *= ((1 + getRandomInt(1, 3) / 100) * advMult); + break; + default: + console.log("ERROR: Un-implemented function index: " + upgN); + break; + } +} + +//Returns how much of a material can be produced based of office productivity (employee stats) +Industry.prototype.getOfficeProductivity = function(office, params) { + var total = office.employeeProd[EmployeePositions.Operations] + + office.employeeProd[EmployeePositions.Engineer] + + office.employeeProd[EmployeePositions.Management], ratio; + if (total === 0) { + ratio = 0; + } else { + ratio = (office.employeeProd[EmployeePositions.Operations] / total) * + (office.employeeProd[EmployeePositions.Engineer] / total) * + (office.employeeProd[EmployeePositions.Management] / total); + ratio = Math.max(0.01, ratio); //Minimum ratio value if you have employees + } + if (params && params.forProduct) { + return ratio * Math.pow(total, 0.25); + } else { + return 2 * ratio * Math.pow(total, 0.35); + } +} + +//Returns a multiplier based on the office' 'Business' employees that affects sales +Industry.prototype.getBusinessFactor = function(office) { + var ratioMult = 1; + if (office.employeeProd["total"] > 0) { + ratioMult = 1 + (office.employeeProd[EmployeePositions.Business] / office.employeeProd["total"]); + } + return ratioMult * Math.pow(1 + office.employeeProd[EmployeePositions.Business], 0.25); +} + +//Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This +//multiplier affects sales. The result is: +// [Total sales mult, total awareness mult, total pop mult, awareness/pop ratio mult] +Industry.prototype.getAdvertisingFactors = function() { + var awarenessFac = Math.pow(this.awareness + 1, this.advFac); + var popularityFac = Math.pow(this.popularity + 1, this.advFac); + var ratioFac = (this.awareness === 0 ? 0.01 : Math.max((this.popularity + .001) / this.awareness, 0.01)); + var totalFac = Math.pow(awarenessFac * popularityFac * ratioFac, 0.85); + return [totalFac, awarenessFac, popularityFac, ratioFac]; +} + +//Returns a multiplier based on a materials demand and competition that affects sales +Industry.prototype.getMarketFactor = function(mat) { + return mat.dmd * (100 - mat.cmp)/100; +} + +// Returns a boolean indicating whether this Industry has the specified Research +Industry.prototype.hasResearch = function(name) { + return (this.researched[name] === true); +} + +Industry.prototype.updateResearchTree = function() { + const researchTree = IndustryResearchTrees[this.type]; + + // Since ResearchTree data isnt saved, we'll update the Research Tree data + // based on the stored 'researched' property in the Industry object + if (Object.keys(researchTree.researched).length !== Object.keys(this.researched).length) { + console.log("Updating Corporation Research Tree Data"); + for (let research in this.researched) { + researchTree.research(research); + } + } +} + +// Get multipliers from Research +Industry.prototype.getAdvertisingMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getAdvertisingMultiplier(); +} + +Industry.prototype.getEmployeeChaMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getEmployeeChaMultiplier(); +} + +Industry.prototype.getEmployeeCreMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getEmployeeCreMultiplier(); +} + +Industry.prototype.getEmployeeEffMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getEmployeeEffMultiplier(); +} + +Industry.prototype.getEmployeeIntMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getEmployeeIntMultiplier(); +} + +Industry.prototype.getProductionMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getProductionMultiplier(); +} + +Industry.prototype.getProductProductionMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getProductProductionMultiplier(); +} + +Industry.prototype.getSalesMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getSalesMultiplier(); +} + +Industry.prototype.getScientificResearchMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getScientificResearchMultiplier(); +} + +Industry.prototype.getStorageMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getStorageMultiplier(); +} + +// Create the Research Tree UI for this Industry +Industry.prototype.createResearchBox = function() { + const boxId = "corporation-research-popup-box"; + + if (researchTreeBoxOpened) { + // It's already opened, so delete it to refresh content + removeElementById(boxId); + researchTreeBox = null; + } + + this.updateResearchTree(); + const researchTree = IndustryResearchTrees[this.type]; + + + // Create the popup first, so that the tree diagram can be added to it + // This is handled by Treant + researchTreeBox = createPopup(boxId, [], { backgroundColor: "black" }); + + // Get the tree's markup (i.e. config) for Treant + const markup = researchTree.createTreantMarkup(); + markup.chart.container = "#" + boxId + "-content"; + markup.chart.nodeAlign = "BOTTOM"; + markup.chart.rootOrientation = "WEST"; + markup.chart.siblingSeparation = 40; + markup.chart.connectors = { + type: "step", + style: { + "arrow-end": "block-wide-long", + "stroke": "white", + "stroke-width": 2, + }, + } + + // Construct the tree with Treant + const treantTree = new Treant(markup); + + // Add Event Listeners for all Nodes + const allResearch = researchTree.getAllNodes(); + for (let i = 0; i < allResearch.length; ++i) { + // If this is already Researched, skip it + if (this.researched[allResearch[i]] === true) { + continue; + } + + // Get the Research object + const research = ResearchMap[allResearch[i]]; + + // Get the DOM Element to add a click listener to it + const sanitizedName = allResearch[i].replace(/\s/g, ''); + const div = document.getElementById(sanitizedName + "-corp-research-click-listener"); + if (div == null) { + console.warn(`Could not find Research Tree div for ${sanitizedName}`); + continue; + } + + div.addEventListener("click", () => { + if (this.sciResearch.qty >= research.cost) { + this.sciResearch.qty -= research.cost; + + // Get the Node from the Research Tree and set its 'researched' property + researchTree.research(allResearch[i]); + this.researched[allResearch[i]] = true; + + return this.createResearchBox(); + } else { + dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`); + } + }); + } + + + const boxContent = document.getElementById(`${boxId}-content`); + if (boxContent != null) { + // Add information about multipliers from research at the bottom of the popup + appendLineBreaks(boxContent, 2); + boxContent.appendChild(createElement("pre", { + display: "block", + innerText: `Multipliers from research:\n` + + ` * Advertising Multiplier: x${researchTree.getAdvertisingMultiplier()}\n` + + ` * Employee Charisma Multiplier: x${researchTree.getEmployeeChaMultiplier()}\n` + + ` * Employee Creativity Multiplier: x${researchTree.getEmployeeCreMultiplier()}\n` + + ` * Employee Efficiency Multiplier: x${researchTree.getEmployeeEffMultiplier()}\n` + + ` * Employee Intelligence Multiplier: x${researchTree.getEmployeeIntMultiplier()}\n` + + ` * Production Multiplier: x${researchTree.getProductionMultiplier()}\n` + + ` * Sales Multiplier: x${researchTree.getSalesMultiplier()}\n` + + ` * Scientific Research Multiplier: x${researchTree.getScientificResearchMultiplier()}\n` + + ` * Storage Multiplier: x${researchTree.getStorageMultiplier()}`, + })); + + // Close button + boxContent.appendChild(createPopupCloseButton(researchTreeBox, { + class: "std-button", + display: "block", + innerText: "Close", + })); + } + + researchTreeBoxOpened = true; +} + +Industry.prototype.toJSON = function() { + return Generic_toJSON("Industry", this); +} + +Industry.fromJSON = function(value) { + return Generic_fromJSON(Industry, value.data); +} + +Reviver.constructors.Industry = Industry; + +function Employee(params={}) { + if (!(this instanceof Employee)) { + return new Employee(params); + } + this.name = params.name ? params.name : "Bobby"; + + //Morale, happiness, and energy are 0-100 + this.mor = params.morale ? params.morale : getRandomInt(50, 100); + this.hap = params.happiness ? params.happiness : getRandomInt(50, 100); + this.ene = params.energy ? params.energy : getRandomInt(50, 100); + + this.age = params.age ? params.age : getRandomInt(20, 50); + this.int = params.intelligence ? params.intelligence : getRandomInt(10, 50); + this.cha = params.charisma ? params.charisma : getRandomInt(10, 50); + this.exp = params.experience ? params.experience : getRandomInt(10, 50); + this.cre = params.creativity ? params.creativity : getRandomInt(10, 50); + this.eff = params.efficiency ? params.efficiency : getRandomInt(10, 50); + this.sal = params.salary ? params.salary : getRandomInt(0.1, 5); + this.pro = 0; //Productivity, This is calculated + + this.cyclesUntilRaise = CyclesPerEmployeeRaise; + + this.loc = params.loc ? params.loc : ""; + this.pos = EmployeePositions.Unassigned; +} + +//Returns the amount the employee needs to be paid +Employee.prototype.process = function(marketCycles=1, office) { + var gain = 0.001 * marketCycles, + det = gain * Math.random(); + this.age += gain; + this.exp += gain; + if (this.age > 150) { + this.int -= det; + this.eff -= det; + this.cha -= det; + } + + // Employee salaries slowly go up over time + this.cyclesUntilRaise -= marketCycles; + if (this.cyclesUntilRaise <= 0) { + this.salary += EmployeeRaiseAmount; + this.cyclesUntilRaise += CyclesPerEmployeeRaise; + } + + //Training + var trainingEff = gain * Math.random(); + if (this.pos === EmployeePositions.Training) { + //To increase creativity and intelligence special upgrades are needed + this.cha += trainingEff; + this.exp += trainingEff; + this.eff += trainingEff; + } + + //Weight based on how full office is + //Too many employees = more likely to decrease energy and happiness + var officeCapacityWeight = 0.5 * (office.employees.length / office.size - 0.5); + if (Math.random() < 0.5 - officeCapacityWeight) { + this.ene += det; + this.hap += det; + } else { + this.ene -= det; + this.hap -= det; + } + if (this.ene < office.minEne) {this.ene = office.minEne;} + if (this.hap < office.minHap) {this.hap = office.minHap;} + var salary = this.sal * marketCycles * SecsPerMarketCycle; + return salary; +} + +Employee.prototype.calculateProductivity = function(corporation, industry) { + var effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), + effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), + effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), + effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier(); + const prodBase = this.mor * this.hap * this.ene * 1e-6; + let prodMult; + switch(this.pos) { + //Calculate productivity based on position. This is multipled by prodBase + //to get final value + case EmployeePositions.Operations: + prodMult = (0.6 * effInt) + (0.1 * effCha) + (this.exp) + + (0.5 * effCre) + (effEff); + break; + case EmployeePositions.Engineer: + prodMult = (effInt) + (0.1 * effCha) + (1.5 * this.exp) + + (effEff); + break; + case EmployeePositions.Business: + prodMult = (0.4 * effInt) + (effCha) + (0.5 * this.exp); + break; + case EmployeePositions.Management: + prodMult = (2 * effCha) + (this.exp) + (0.2 * effCre) + + (0.7 * effEff); + break; + case EmployeePositions.RandD: + prodMult = (1.5 * effInt) + (0.8 * this.exp) + (effCre) + + (0.5 * effEff); + break; + case EmployeePositions.Unassigned: + case EmployeePositions.Training: + prodMult = 0; + break; + default: + console.log("ERROR: Invalid employee position: " + this.pos); + break; + } + return prodBase * prodMult; +} + +//Process benefits from having an office party thrown +Employee.prototype.throwParty = function(money) { + var mult = 1 + (money / 10e6); + this.mor *= mult; + this.mor = Math.min(100, this.mor); + this.hap *= mult; + this.hap = Math.min(100, this.hap); + return mult; +} + +//'panel' is the DOM element on which to create the UI +Employee.prototype.createUI = function(panel, corporation, industry) { + var effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), + effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), + effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), + effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier(); + panel.style.color = "white"; + panel.appendChild(createElement("p", { + id:"cmpy-mgmt-employee-" + this.name + "-panel-text", + innerHTML:"Morale: " + formatNumber(this.mor, 3) + "
" + + "Happiness: " + formatNumber(this.hap, 3) + "
" + + "Energy: " + formatNumber(this.ene, 3) + "
" + + "Age: " + formatNumber(this.age, 3) + "
" + + "Intelligence: " + formatNumber(effInt, 3) + "
" + + "Charisma: " + formatNumber(effCha, 3) + "
" + + "Experience: " + formatNumber(this.exp, 3) + "
" + + "Creativity: " + formatNumber(effCre, 3) + "
" + + "Efficiency: " + formatNumber(effEff, 3) + "
" + + "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s
", + })); + + //Selector for employee position + var selector = createElement("select", {}); + for (var key in EmployeePositions) { + if (EmployeePositions.hasOwnProperty(key)) { + selector.add(createElement("option", { + text: EmployeePositions[key], + value: EmployeePositions[key], + })); + } + } + + selector.addEventListener("change", ()=>{ + this.pos = selector.options[selector.selectedIndex].value; + }); + + //Set initial value of selector + for (var i = 0; i < selector.length; ++i) { + if (selector.options[i].value === this.pos) { + selector.selectedIndex = i; + break; + } + } + panel.appendChild(selector); +} + +Employee.prototype.updateUI = function(panel, corporation, industry) { + var effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), + effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), + effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), + effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier(); + if (panel == null) { + console.log("ERROR: Employee.updateUI() called with null panel"); + return; + } + var text = document.getElementById("cmpy-mgmt-employee-" + this.name + "-panel-text"); + if (text == null) { + return this.createUI(panel); + } + text.innerHTML = "Morale: " + formatNumber(this.mor, 3) + "
" + + "Happiness: " + formatNumber(this.hap, 3) + "
" + + "Energy: " + formatNumber(this.ene, 3) + "
" + + "Age: " + formatNumber(this.age, 3) + "
" + + "Intelligence: " + formatNumber(effInt, 3) + "
" + + "Charisma: " + formatNumber(effCha, 3) + "
" + + "Experience: " + formatNumber(this.exp, 3) + "
" + + "Creativity: " + formatNumber(effCre, 3) + "
" + + "Efficiency: " + formatNumber(effEff, 3) + "
" + + "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s
"; +} + +Employee.prototype.toJSON = function() { + return Generic_toJSON("Employee", this); +} + +Employee.fromJSON = function(value) { + return Generic_fromJSON(Employee, value.data); +} + +Reviver.constructors.Employee = Employee; + +var OfficeSpaceTiers = { + Basic: "Basic", + Enhanced: "Enhanced", + Luxurious: "Luxurious", + Extravagant: "Extravagant" +} + +function OfficeSpace(params={}) { + this.loc = params.loc ? params.loc : ""; + this.cost = params.cost ? params.cost : 1; + this.size = params.size ? params.size : 1; + this.comf = params.comfort ? params.comfort : 1; + this.beau = params.beauty ? params.beauty : 1; + this.tier = OfficeSpaceTiers.Basic; + + // Min/max energy of employees + this.minEne = 0; + this.maxEne = 100; + + // Min/max Happiness of office + this.minHap = 0; + this.maxHap = 100; + + // Maximum Morale of office + this.maxMor = 100; + + this.employees = []; + this.employeeProd = { + [EmployeePositions.Operations]: 0, + [EmployeePositions.Engineer]: 0, + [EmployeePositions.Business]: 0, + [EmployeePositions.Management]: 0, + [EmployeePositions.RandD]: 0, + total: 0, + }; +} + +OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) { + var corporation = parentRefs.corporation, industry = parentRefs.industry; + + // Process Office properties + this.maxEne = 100; + this.maxHap = 100; + this.maxMor = 100; + if (industry.hasResearch("Go-Juice")) { + this.maxEne += 10; + } + if (industry.hasResearch("JoyWire")) { + this.maxHap += 10; + } + if (industry.hasResearch("Sti.mu")) { + this.maxMor += 10; + } + + // Calculate changes in Morale/Happiness/Energy for Employees + var perfMult=1; //Multiplier for employee morale/happiness/energy based on company performance + if (industry.funds < 0 && industry.lastCycleRevenue < 0) { + perfMult = Math.pow(0.99, marketCycles); + } else if (industry.funds > 0 && industry.lastCycleRevenue > 0) { + perfMult = Math.pow(1.01, marketCycles); + } + + const hasAutobrew = industry.hasResearch("AutoBrew"); + const hasAutoparty = industry.hasResearch("AutoPartyManager"); + + var salaryPaid = 0; + for (let i = 0; i < this.employees.length; ++i) { + const emp = this.employees[i]; + if (hasAutoparty) { + emp.mor = this.maxMor; + emp.hap = this.maxHap; + } else { + emp.mor *= perfMult; + emp.hap *= perfMult; + emp.mor = Math.min(emp.mor, this.maxMor); + emp.hap = Math.min(emp.hap, this.maxHap); + } + + if (hasAutobrew) { + emp.ene = this.maxEne; + } else { + emp.ene *= perfMult; + emp.ene = Math.min(emp.ene, this.maxEne); + } + + const salary = emp.process(marketCycles, this); + salaryPaid += salary; + } + + this.calculateEmployeeProductivity(marketCycles, parentRefs); + return salaryPaid; +} + +OfficeSpace.prototype.calculateEmployeeProductivity = function(marketCycles=1, parentRefs) { + var company = parentRefs.corporation, industry = parentRefs.industry; + + //Reset + for (const name in this.employeeProd) { + if (this.employeeProd.hasOwnProperty(name)) { + this.employeeProd[name] = 0; + } + } + + var total = 0; + for (let i = 0; i < this.employees.length; ++i) { + const employee = this.employees[i]; + const prod = employee.calculateProductivity(company, industry); + this.employeeProd[employee.pos] += prod; + total += prod; + } + this.employeeProd["total"] = total; +} + +//Takes care of UI as well +OfficeSpace.prototype.findEmployees = function(parentRefs) { + var company = parentRefs.corporation, division = parentRefs.industry; + if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} + + //Generate three random employees (meh, decent, amazing) + var mult1 = getRandomInt(25, 50)/100, + mult2 = getRandomInt(51, 75)/100, + mult3 = getRandomInt(76, 100)/100; + var int = getRandomInt(50, 100), + cha = getRandomInt(50, 100), + exp = getRandomInt(50, 100), + cre = getRandomInt(50, 100), + eff = getRandomInt(50, 100), + sal = EmployeeSalaryMultiplier * (int + cha + exp + cre + eff); + + var emp1 = new Employee({ + intelligence: int * mult1, + charisma: cha * mult1, + experience: exp * mult1, + creativity: cre * mult1, + efficiency: eff * mult1, + salary: sal * mult1, + }); + + var emp2 = new Employee({ + intelligence: int * mult2, + charisma: cha * mult2, + experience: exp * mult2, + creativity: cre * mult2, + efficiency: eff * mult2, + salary: sal * mult2, + }); + + var emp3 = new Employee({ + intelligence: int * mult3, + charisma: cha * mult3, + experience: exp * mult3, + creativity: cre * mult3, + efficiency: eff * mult3, + salary: sal * mult3, + }); + + var text = createElement("h1", { + innerHTML: "Select one of the following candidates for hire:", + }); + + var createEmpDiv = function(employee, office) { + var div = createElement("div", { + class:"cmpy-mgmt-find-employee-option", + innerHTML: "Intelligence: " + formatNumber(employee.int, 1) + "
" + + "Charisma: " + formatNumber(employee.cha, 1) + "
" + + "Experience: " + formatNumber(employee.exp, 1) + "
" + + "Creativity: " + formatNumber(employee.cre, 1) + "
" + + "Efficiency: " + formatNumber(employee.eff, 1) + "
" + + "Salary: " + numeralWrapper.format(employee.sal, '$0.000a') + " \ s
", + clickListener:()=>{ + office.hireEmployee(employee, parentRefs); + removeElementById("cmpy-mgmt-hire-employee-popup"); + return false; + } + }); + return div; + }; + + var cancelBtn = createElement("a", { + class:"a-link-button", + innerText:"Cancel", + float:"right", + clickListener:()=>{ + removeElementById("cmpy-mgmt-hire-employee-popup"); + return false; + } + }); + + var elems = [text, + createEmpDiv(emp1, this), + createEmpDiv(emp2, this), + createEmpDiv(emp3, this), + cancelBtn]; + + createPopup("cmpy-mgmt-hire-employee-popup", elems); +} + +OfficeSpace.prototype.hireEmployee = function(employee, parentRefs) { + var company = parentRefs.corporation, division = parentRefs.industry; + var yesBtn = yesNoTxtInpBoxGetYesButton(), + noBtn = yesNoTxtInpBoxGetNoButton(); + yesBtn.innerHTML = "Hire"; + noBtn.innerHTML = "Cancel"; + yesBtn.addEventListener("click", () => { + var name = yesNoTxtInpBoxGetInput(); + for (var i = 0; i < this.employees.length; ++i) { + if (this.employees[i].name === name) { + dialogBoxCreate("You already have an employee with this nickname! Please give every employee a unique nickname."); + return false; + } + } + employee.name = name; + this.employees.push(employee); + company.displayDivisionContent(division, currentCityUi); + return yesNoTxtInpBoxClose(); + }); + noBtn.addEventListener("click", ()=>{ + return yesNoTxtInpBoxClose(); + }); + yesNoTxtInpBoxCreate("Give your employee a nickname!"); +} + +OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) { + var company = parentRefs.corporation, division = parentRefs.industry; + if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} + + //Generate three random employees (meh, decent, amazing) + var mult = getRandomInt(76, 100)/100; + var int = getRandomInt(50, 100), + cha = getRandomInt(50, 100), + exp = getRandomInt(50, 100), + cre = getRandomInt(50, 100), + eff = getRandomInt(50, 100), + sal = EmployeeSalaryMultiplier * (int + cha + exp + cre + eff); + + var emp = new Employee({ + intelligence: int * mult, + charisma: cha * mult, + experience: exp * mult, + creativity: cre * mult, + efficiency: eff * mult, + salary: sal * mult, + }); + + var name = generateRandomString(7); + + for (var i = 0; i < this.employees.length; ++i) { + if (this.employees[i].name === name) { + return this.hireRandomEmployee(parentRefs); + } + } + emp.name = name; + this.employees.push(emp); + company.displayDivisionContent(division, currentCityUi); +} + +//Finds the first unassigned employee and assigns its to the specified job +OfficeSpace.prototype.assignEmployeeToJob = function(job) { + for (var i = 0; i < this.employees.length; ++i) { + if (this.employees[i].pos === EmployeePositions.Unassigned) { + this.employees[i].pos = job; + return true; + } + } + return false; +} + +//Finds the first employee with the given job and unassigns it +OfficeSpace.prototype.unassignEmployeeFromJob = function(job) { + for (var i = 0; i < this.employees.length; ++i) { + if (this.employees[i].pos === job) { + this.employees[i].pos = EmployeePositions.Unassigned; + return true; + } + } + return false; +} + +OfficeSpace.prototype.toJSON = function() { + return Generic_toJSON("OfficeSpace", this); +} + +OfficeSpace.fromJSON = function(value) { + return Generic_fromJSON(OfficeSpace, value.data); +} + +Reviver.constructors.OfficeSpace = OfficeSpace; + +function Corporation(params={}) { + this.name = params.name ? params.name : "The Corporation"; + + //A division/business sector is represented by the object: + this.divisions = []; + + //Financial stats + this.funds = new Decimal(150e9); + this.revenue = new Decimal(0); + this.expenses = new Decimal(0); + this.fundingRound = 0; + this.public = false; //Publicly traded + this.totalShares = INITIALSHARES; // Total existing shares + this.numShares = INITIALSHARES; // Total shares owned by player + this.shareSalesUntilPriceUpdate = SHARESPERPRICEUPDATE; + this.shareSaleCooldown = 0; // Game cycles until player can sell shares again + this.issueNewSharesCooldown = 0; // Game cycles until player can issue shares again + this.dividendPercentage = 0; + this.dividendTaxPercentage = 50; + this.issuedShares = 0; + this.sharePrice = 0; + this.storedCycles = 0; + + var numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length, + numUpgrades = Object.keys(CorporationUpgrades).length; + + this.unlockUpgrades = Array(numUnlockUpgrades).fill(0); + this.upgrades = Array(numUpgrades).fill(0); + this.upgradeMultipliers = Array(numUpgrades).fill(1); + + this.state = new CorporationState(); +} + +Corporation.prototype.getState = function() { + return this.state.getState(); +} + +Corporation.prototype.storeCycles = function(numCycles=1) { + this.storedCycles += numCycles; +} + +Corporation.prototype.process = function() { + var corp = this; + if (this.storedCycles >= CyclesPerIndustryStateCycle) { + const state = this.getState(); + const marketCycles = 1; + const gameCycles = (marketCycles * CyclesPerIndustryStateCycle); + this.storedCycles -= gameCycles; + + this.divisions.forEach(function(ind) { + ind.process(marketCycles, state, corp); + }); + + // Process cooldowns + if (this.shareSaleCooldown > 0) { + this.shareSaleCooldown -= gameCycles; + } + if (this.issueNewSharesCooldown > 0) { + this.issueNewSharesCooldown -= gameCycles; + } + + //At the start of a new cycle, calculate profits from previous cycle + if (state === "START") { + this.revenue = new Decimal(0); + this.expenses = new Decimal(0); + this.divisions.forEach((ind) => { + if (ind.lastCycleRevenue === -Infinity || ind.lastCycleRevenue === Infinity) { return; } + if (ind.lastCycleExpenses === -Infinity || ind.lastCycleExpenses === Infinity) { return; } + this.revenue = this.revenue.plus(ind.lastCycleRevenue); + this.expenses = this.expenses.plus(ind.lastCycleExpenses); + }); + var profit = this.revenue.minus(this.expenses); + const cycleProfit = profit.times(marketCycles * SecsPerMarketCycle); + if (isNaN(this.funds)) { + dialogBoxCreate("There was an error calculating your Corporations funds and they got reset to 0. " + + "This is a bug. Please report to game developer.

" + + "(Your funds have been set to $150b for the inconvenience)"); + this.funds = new Decimal(150e9); + } + + // Process dividends + if (this.dividendPercentage > 0 && cycleProfit > 0) { + // Validate input again, just to be safe + if (isNaN(this.dividendPercentage) || this.dividendPercentage < 0 || this.dividendPercentage > DividendMaxPercentage) { + console.error(`Invalid Corporation dividend percentage: ${this.dividendPercentage}`); + } else { + const totalDividends = (this.dividendPercentage / 100) * cycleProfit; + const retainedEarnings = cycleProfit - totalDividends; + const dividendsPerShare = totalDividends / this.totalShares; + const profit = this.numShares * dividendsPerShare * (1 - (this.dividendTaxPercentage / 100)); + Player.gainMoney(profit); + Player.recordMoneySource(profit, "corporation"); + this.funds = this.funds.plus(retainedEarnings); + } + } else { + this.funds = this.funds.plus(cycleProfit); + } + + this.updateSharePrice(); + } + + this.state.nextState(); + + if (routing.isOn(Page.Corporation)) { this.rerender(); } + } +} + +Corporation.prototype.determineValuation = function() { + var val, profit = (this.revenue.minus(this.expenses)).toNumber(); + if (this.public) { + // Account for dividends + if (this.dividendPercentage > 0) { + profit *= ((100 - this.dividendPercentage) / 100); + } + + val = this.funds.toNumber() + (profit * 85e3); + val *= (Math.pow(1.1, this.divisions.length)); + val = Math.max(val, 0); + } else { + val = 10e9 + Math.max(this.funds.toNumber(), 0) / 3; //Base valuation + if (profit > 0) { + val += (profit * 315e3); + val *= (Math.pow(1.1, this.divisions.length)); + } else { + val = 10e9 * Math.pow(1.1, this.divisions.length); + } + val -= (val % 1e6); //Round down to nearest millionth + } + return val * BitNodeMultipliers.CorporationValuation; +} + +Corporation.prototype.getInvestment = function() { + var val = this.determineValuation(), percShares; + let roundMultiplier = 4; + switch (this.fundingRound) { + case 0: //Seed + percShares = 0.10; + roundMultiplier = 5; + break; + case 1: //Series A + percShares = 0.35; + roundMultiplier = 4; + break; + case 2: //Series B + percShares = 0.25; + roundMultiplier = 4; + break; + case 3: //Series C + percShares = 0.20; + roundMultiplier = 3.5; + break; + case 4: + return; + } + var funding = val * percShares * roundMultiplier, + investShares = Math.floor(INITIALSHARES * percShares), + yesBtn = yesNoBoxGetYesButton(), + noBtn = yesNoBoxGetNoButton(); + yesBtn.innerHTML = "Accept"; + noBtn.innerHML = "Reject"; + yesBtn.addEventListener("click", ()=>{ + ++this.fundingRound; + this.funds = this.funds.plus(funding); + this.numShares -= investShares; + this.displayCorporationOverviewContent(); + return yesNoBoxClose(); + }); + noBtn.addEventListener("click", ()=>{ + return yesNoBoxClose(); + }); + yesNoBoxCreate("An investment firm has offered you " + numeralWrapper.format(funding, '$0.000a') + + " in funding in exchange for a " + numeralWrapper.format(percShares*100, "0.000a") + + "% stake in the company (" + numeralWrapper.format(investShares, '0.000a') + " shares).

" + + "Do you accept or reject this offer?

" + + "Hint: Investment firms will offer more money if your corporation is turning a profit"); +} + +Corporation.prototype.goPublic = function() { + var goPublicPopupId = "cmpy-mgmt-go-public-popup"; + var initialSharePrice = this.determineValuation() / (this.totalShares); + var txt = createElement("p", { + innerHTML: "Enter the number of shares you would like to issue " + + "for your IPO. These shares will be publicly sold " + + "and you will no longer own them. Your Corporation will receive " + + numeralWrapper.format(initialSharePrice, '$0.000a') + " per share " + + "(the IPO money will be deposited directly into your Corporation's funds).

" + + "You have a total of " + numeralWrapper.format(this.numShares, "0.000a") + " of shares that you can issue.", + }); + var yesBtn; + var input = createElement("input", { + type:"number", + placeholder: "Shares to issue", + onkeyup:(e)=>{ + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {yesBtn.click();} + } + }); + var br = createElement("br", {}); + yesBtn = createElement("a", { + class:"a-link-button", + innerText:"Go Public", + clickListener:()=>{ + var numShares = Math.round(input.value); + var initialSharePrice = this.determineValuation() / (this.totalShares); + if (isNaN(numShares)) { + dialogBoxCreate("Invalid value for number of issued shares"); + return false; + } + if (numShares > this.numShares) { + dialogBoxCreate("Error: You don't have that many shares to issue!"); + return false; + } + this.public = true; + this.sharePrice = initialSharePrice; + this.issuedShares = numShares; + this.numShares -= numShares; + this.funds = this.funds.plus(numShares * initialSharePrice); + this.displayCorporationOverviewContent(); + removeElementById(goPublicPopupId); + dialogBoxCreate(`You took your ${this.name} public and earned ` + + `${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`); + return false; + } + }); + var noBtn = createElement("a", { + class:"a-link-button", + innerText:"Cancel", + clickListener:()=>{ + removeElementById(goPublicPopupId); + return false; + } + }); + createPopup(goPublicPopupId, [txt, br, input, yesBtn, noBtn]); +} + +Corporation.prototype.getTargetSharePrice = function() { + // Note: totalShares - numShares is not the same as issuedShares because + // issuedShares does not account for private investors + return this.determineValuation() / (2 * (this.totalShares - this.numShares) + 1); +} + +Corporation.prototype.updateSharePrice = function() { + const targetPrice = this.getTargetSharePrice(); + if (this.sharePrice <= targetPrice) { + this.sharePrice *= (1 + (Math.random() * 0.01)); + } else { + this.sharePrice *= (1 - (Math.random() * 0.01)); + } + if (this.sharePrice <= 0.01) {this.sharePrice = 0.01;} +} + +Corporation.prototype.immediatelyUpdateSharePrice = function() { + this.sharePrice = this.getTargetSharePrice(); +} + +// Calculates how much money will be made and what the resulting stock price +// will be when the player sells his/her shares +// @return - [Player profit, final stock price, end shareSalesUntilPriceUpdate property] +Corporation.prototype.calculateShareSale = function(numShares) { + let sharesTracker = numShares; + let sharesUntilUpdate = this.shareSalesUntilPriceUpdate; + let sharePrice = this.sharePrice; + let sharesSold = 0; + let profit = 0; + + const maxIterations = Math.ceil(numShares / SHARESPERPRICEUPDATE); + if (isNaN(maxIterations) || maxIterations > 10e6) { + console.error(`Something went wrong or unexpected when calculating share sale. Maxiterations calculated to be ${maxIterations}`); + return; + } + + for (let i = 0; i < maxIterations; ++i) { + if (sharesTracker < sharesUntilUpdate) { + profit += (sharePrice * sharesTracker); + sharesUntilUpdate -= sharesTracker; + break; + } else { + profit += (sharePrice * sharesUntilUpdate); + sharesUntilUpdate = SHARESPERPRICEUPDATE; + sharesTracker -= sharesUntilUpdate; + sharesSold += sharesUntilUpdate; + + // Calculate what new share price would be + sharePrice = this.determineValuation() / (2 * (this.totalShares + sharesSold - this.numShares)); + } + } + + return [profit, sharePrice, sharesUntilUpdate]; +} + +Corporation.prototype.convertCooldownToString = function(cd) { + // The cooldown value is based on game cycles. Convert to a simple string + const CyclesPerSecond = 1000 / CONSTANTS.MilliPerCycle; + const seconds = cd / 5; + + const SecondsPerMinute = 60; + const SecondsPerHour = 3600; + + if (seconds > SecondsPerHour) { + return `${Math.floor(seconds / SecondsPerHour)} hour(s)`; + } else if (seconds > SecondsPerMinute) { + return `${Math.floor(seconds / SecondsPerMinute)} minute(s)`; + } else { + return `${Math.floor(seconds)} second(s)`; + } +} + +//One time upgrades that unlock new features +Corporation.prototype.unlock = function(upgrade) { + const upgN = upgrade[0], price = upgrade[1]; + while (this.unlockUpgrades.length <= upgN) { + this.unlockUpgrades.push(0); + } + if (this.funds.lt(price)) { + dialogBoxCreate("You don't have enough funds to unlock this!"); + return; + } + this.unlockUpgrades[upgN] = 1; + this.funds = this.funds.minus(price); + + // Apply effects for one-time upgrades + if (upgN === 5) { + this.dividendTaxPercentage -= 5; + } else if (upgN === 6) { + this.dividendTaxPercentage -= 10; + } +} + +//Levelable upgrades +Corporation.prototype.upgrade = function(upgrade) { + var upgN = upgrade[0], basePrice = upgrade[1], priceMult = upgrade[2], + upgradeAmt = upgrade[3]; //Amount by which the upgrade multiplier gets increased (additive) + while (this.upgrades.length <= upgN) {this.upgrades.push(0);} + while (this.upgradeMultipliers.length <= upgN) {this.upgradeMultipliers.push(1);} + var totalCost = basePrice * Math.pow(priceMult, this.upgrades[upgN]); + if (this.funds.lt(totalCost)) { + dialogBoxCreate("You don't have enough funds to purchase this!"); + return; + } + ++this.upgrades[upgN]; + this.funds = this.funds.minus(totalCost); + + //Increase upgrade multiplier + this.upgradeMultipliers[upgN] = 1 + (this.upgrades[upgN] * upgradeAmt); + + //If storage size is being updated, update values in Warehouse objects + if (upgN === 1) { + for (var i = 0; i < this.divisions.length; ++i) { + var industry = this.divisions[i]; + for (var city in industry.warehouses) { + if (industry.warehouses.hasOwnProperty(city) && industry.warehouses[city] instanceof Warehouse) { + industry.warehouses[city].updateSize(this, industry); + } + } + } + } + + this.updateCorporationOverviewContent(); +} + +Corporation.prototype.getProductionMultiplier = function() { + var mult = this.upgradeMultipliers[0]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getStorageMultiplier = function() { + var mult = this.upgradeMultipliers[1]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getDreamSenseGain = function() { + var gain = this.upgradeMultipliers[2] - 1; + return gain <= 0 ? 0 : gain; +} + +Corporation.prototype.getAdvertisingMultiplier = function() { + var mult = this.upgradeMultipliers[3]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getEmployeeCreMultiplier = function() { + var mult = this.upgradeMultipliers[4]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getEmployeeChaMultiplier = function() { + var mult = this.upgradeMultipliers[5]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getEmployeeIntMultiplier = function() { + var mult = this.upgradeMultipliers[6]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getEmployeeEffMultiplier = function() { + var mult = this.upgradeMultipliers[7]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getSalesMultiplier = function() { + var mult = this.upgradeMultipliers[8]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getScientificResearchMultiplier = function() { + var mult = this.upgradeMultipliers[9]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +// Adds the Corporation Handbook (Starter Guide) to the player's home computer. +// This is a lit file that gives introductory info to the player +// This occurs when the player clicks the "Getting Started Guide" button on the overview panel +Corporation.prototype.getStarterGuide = function() { + // Check if player already has Corporation Handbook + let homeComp = Player.getHomeComputer(), + hasHandbook = false, + handbookFn = "corporation-management-handbook.lit"; + for (let i = 0; i < homeComp.messages.length; ++i) { + if (isString(homeComp.messages[i]) && homeComp.messages[i] === handbookFn) { + hasHandbook = true; + break; + } + } + + if (!hasHandbook) { homeComp.messages.push(handbookFn); } + showLiterature(handbookFn); + return false; +} + +let corpRouting; +let eventHandler; +let companyManagementDiv; +Corporation.prototype.createUI = function() { + companyManagementDiv = createElement("div", { + id:"cmpy-mgmt-container", + position:"fixed", + class:"generic-menupage-container" + }); + document.getElementById("entire-game-container").appendChild(companyManagementDiv); + + corpRouting = new CorporationRouting(this); + eventHandler = new CorporationEventHandler(this, corpRouting); + + this.rerender(); +} + +Corporation.prototype.rerender = function() { + if (companyManagementDiv == null || corpRouting == null || eventHandler == null) { + console.warn(`Corporation.rerender() called when companyManagementDiv, corpRouting, or eventHandler is null`); + return; + } + if (!routing.isOn(Page.Corporation)) { return; } + + console.log("Re-rendering..."); + + ReactDOM.render(, companyManagementDiv); +} + +Corporation.prototype.clearUI = function() { + if (companyManagementDiv instanceof HTMLElement) { + ReactDOM.unmountComponentAtNode(companyManagementDiv); + removeElementById(companyManagementDiv.id); + } + + companyManagementDiv = null; + document.getElementById("character-overview-wrapper").style.visibility = "visible"; +} + +Corporation.prototype.toJSON = function() { + return Generic_toJSON("Corporation", this); +} + +Corporation.fromJSON = function(value) { + return Generic_fromJSON(Corporation, value.data); +} + +Reviver.constructors.Corporation = Corporation; + +export {Corporation, Industry, OfficeSpace, Warehouse}; diff --git a/src/Corporation/IndustryData.ts b/src/Corporation/IndustryData.ts index 994f4aeb6..699e24a53 100644 --- a/src/Corporation/IndustryData.ts +++ b/src/Corporation/IndustryData.ts @@ -1,5 +1,6 @@ import { ResearchTree } from "./ResearchTree"; -import { getBaseResearchTreeCopy } from "./data/BaseResearchTree"; +import { getBaseResearchTreeCopy, + getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree"; import { numeralWrapper } from "../ui/numeralFormat"; @@ -112,15 +113,15 @@ export let IndustryResearchTrees: IIndustryMap = { Agriculture: getBaseResearchTreeCopy(), Fishing: getBaseResearchTreeCopy(), Mining: getBaseResearchTreeCopy(), - Food: getBaseResearchTreeCopy(), - Tobacco: getBaseResearchTreeCopy(), + Food: getProductIndustryResearchTreeCopy(), + Tobacco: getProductIndustryResearchTreeCopy(), Chemical: getBaseResearchTreeCopy(), - Pharmaceutical: getBaseResearchTreeCopy(), - Computer: getBaseResearchTreeCopy(), - Robotics: getBaseResearchTreeCopy(), - Software: getBaseResearchTreeCopy(), - Healthcare: getBaseResearchTreeCopy(), - RealEstate: getBaseResearchTreeCopy(), + Pharmaceutical: getProductIndustryResearchTreeCopy(), + Computer: getProductIndustryResearchTreeCopy(), + Robotics: getProductIndustryResearchTreeCopy(), + Software: getProductIndustryResearchTreeCopy(), + Healthcare: getProductIndustryResearchTreeCopy(), + RealEstate: getProductIndustryResearchTreeCopy(), } export function resetIndustryResearchTrees() { diff --git a/src/Corporation/Material.ts b/src/Corporation/Material.ts index 40a75cbe3..30c82c139 100644 --- a/src/Corporation/Material.ts +++ b/src/Corporation/Material.ts @@ -12,7 +12,6 @@ export class Material { return Generic_fromJSON(Material, value.data); } - // Name of material name: string = "InitName"; @@ -64,6 +63,10 @@ export class Material { prdman: any[] = [false, 0]; // Production sllman: any[] = [false, 0]; // Sale + // Flags that signal whether automatic sale pricing through Market TA is enabled + marketTa1: boolean = false; + marketTa2: boolean = false; + constructor(params: IConstructorParams = {}) { if (params.name) { this.name = params.name; } this.init(); diff --git a/src/Corporation/Research.ts b/src/Corporation/Research.ts index dcce1e50c..ee017ec06 100644 --- a/src/Corporation/Research.ts +++ b/src/Corporation/Research.ts @@ -8,6 +8,7 @@ export interface IConstructorParams { employeeEffMult?: number; employeeIntMult?: number; productionMult?: number; + productProductionMult?: number; salesMult?: number; sciResearchMult?: number; storageMult?: number; @@ -30,6 +31,7 @@ export class Research { employeeEffMult: number = 1; employeeIntMult: number = 1; productionMult: number = 1; + productProductionMult: number = 1; salesMult: number = 1; sciResearchMult: number = 1; storageMult: number = 1; @@ -38,14 +40,15 @@ export class Research { this.name = p.name; this.cost = p.cost; this.desc = p.desc; - if (p.advertisingMult) { this.advertisingMult = p.advertisingMult; } - if (p.employeeChaMult) { this.employeeChaMult = p.employeeChaMult; } - if (p.employeeCreMult) { this.employeeCreMult = p.employeeCreMult; } - if (p.employeeEffMult) { this.employeeEffMult = p.employeeEffMult; } - if (p.employeeIntMult) { this.employeeIntMult = p.employeeIntMult; } - if (p.productionMult) { this.productionMult = p.productionMult; } - if (p.salesMult) { this.salesMult = p.salesMult; } - if (p.sciResearchMult) { this.sciResearchMult = p.sciResearchMult; } - if (p.storageMult) { this.storageMult = p.storageMult; } + if (p.advertisingMult) { this.advertisingMult = p.advertisingMult; } + if (p.employeeChaMult) { this.employeeChaMult = p.employeeChaMult; } + if (p.employeeCreMult) { this.employeeCreMult = p.employeeCreMult; } + if (p.employeeEffMult) { this.employeeEffMult = p.employeeEffMult; } + if (p.employeeIntMult) { this.employeeIntMult = p.employeeIntMult; } + if (p.productionMult) { this.productionMult = p.productionMult; } + if (p.productProductionMult) { this.productProductionMult = p.productProductionMult; } + if (p.salesMult) { this.salesMult = p.salesMult; } + if (p.sciResearchMult) { this.sciResearchMult = p.sciResearchMult; } + if (p.storageMult) { this.storageMult = p.storageMult; } } } diff --git a/src/Corporation/ResearchTree.ts b/src/Corporation/ResearchTree.ts index 7e77aa978..9cbdd8afa 100644 --- a/src/Corporation/ResearchTree.ts +++ b/src/Corporation/ResearchTree.ts @@ -185,6 +185,10 @@ export class ResearchTree { return this.getMultiplierHelper("productionMult"); } + getProductProductionMultiplier(): number { + return this.getMultiplierHelper("productProductionMult"); + } + getSalesMultiplier(): number { return this.getMultiplierHelper("salesMult"); } diff --git a/src/Corporation/Warehouse.ts b/src/Corporation/Warehouse.ts new file mode 100644 index 000000000..a6bf7ce27 --- /dev/null +++ b/src/Corporation/Warehouse.ts @@ -0,0 +1,100 @@ +import { Material } from "./Material"; +import { MaterialSizes } from "./MaterialSizes"; +import { IMap } from "../types"; +import { numeralWrapper } from "../ui/numeralFormat"; +import { Generic_fromJSON, + Generic_toJSON, + Reviver } from "../../utils/JSONReviver"; + +interface IConstructorParams { + loc?: string; + size?: number; +} + +interface IParent { + getStorageMultiplier(): number; +} + +export class Warehouse { + // Initiatizes a Warehouse object from a JSON save state. + static fromJSON(value: any): Warehouse { + return Generic_fromJSON(Warehouse, value.data); + } + + // Text that describes how the space in this Warehouse is being used + // Used to create a tooltip in the UI + breakdown: string = ""; + + // Warehouse's level, which affects its maximum size + level: number = 0; + + // City that this Warehouse is in + loc: string; + + // Map of Materials held by this Warehouse + materials: IMap; + + // Maximum amount warehouse can hold + size: number; + + // Amount of space currently used by warehouse + sizeUsed: number = 0; + + // Whether Smart Supply is enabled for this Industry (the Industry that this Warehouse is for) + smartSupplyEnabled: boolean = false; + + // Stores the amount of product to be produced. Used for Smart Supply unlock. + // The production tracked by smart supply is always based on the previous cycle, + // so it will always trail the "true" production by 1 cycle + smartSupplyStore: number = 0; + + constructor(params: IConstructorParams = {}) { + this.loc = params.loc ? params.loc : ""; + this.size = params.size ? params.size : 0; + + this.materials = { + Water: new Material({name: "Water"}), + Energy: new Material({name: "Energy"}), + Food: new Material({name: "Food"}), + Plants: new Material({name: "Plants"}), + Metal: new Material({name: "Metal"}), + Hardware: new Material({name: "Hardware"}), + Chemicals: new Material({name: "Chemicals"}), + Drugs: new Material({name: "Drugs"}), + Robots: new Material({name: "Robots"}), + AICores: new Material({name: "AI Cores"}), + RealEstate: new Material({name: "Real Estate"}) + } + } + + // Re-calculate how much space is being used by this Warehouse + updateMaterialSizeUsed() { + this.sizeUsed = 0; + this.breakdown = ""; + for (const matName in this.materials) { + var mat = this.materials[matName]; + if (MaterialSizes.hasOwnProperty(matName)) { + this.sizeUsed += (mat.qty * MaterialSizes[matName]); + if (mat.qty > 0) { + this.breakdown += (matName + ": " + numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0") + "
"); + } + } + } + if (this.sizeUsed > this.size) { + console.warn("Warehouse size used greater than capacity, something went wrong"); + } + } + + updateSize(corporation: IParent, industry: IParent) { + this.size = (this.level * 100) + * corporation.getStorageMultiplier() + * industry.getStorageMultiplier(); + } + + // Serialize the current object to a JSON save state. + toJSON(): any { + return Generic_toJSON("Warehouse", this); + } +} + +Reviver.constructors.Warehouse = Warehouse; diff --git a/src/Corporation/data/BaseResearchTree.ts b/src/Corporation/data/BaseResearchTree.ts index bdd475d86..a4e535e1c 100644 --- a/src/Corporation/data/BaseResearchTree.ts +++ b/src/Corporation/data/BaseResearchTree.ts @@ -14,14 +14,14 @@ function makeNode(name: string): Node { return new Node({ text: research.name, cost: research.cost }); } - -export function getBaseResearchTreeCopy(): ResearchTree { - const baseResearchTree: ResearchTree = new ResearchTree(); - +// Creates the Nodes for the BaseResearchTree. +// Return the Root Node +function createBaseResearchTreeNodes(): Node { const rootNode: Node = makeNode("Hi-Tech R&D Laboratory"); const autoBrew: Node = makeNode("AutoBrew"); const autoParty: Node = makeNode("AutoPartyManager"); const autoDrugs: Node = makeNode("Automatic Drug Administration"); + const bulkPurchasing: Node = makeNode("Bulk Purchasing"); const cph4: Node = makeNode("CPH4 Injections"); const drones: Node = makeNode("Drones"); const dronesAssembly: Node = makeNode("Drones - Assembly"); @@ -47,13 +47,39 @@ export function getBaseResearchTreeCopy(): ResearchTree { rootNode.addChild(autoBrew); rootNode.addChild(autoParty); rootNode.addChild(autoDrugs); + rootNode.addChild(bulkPurchasing); rootNode.addChild(drones); rootNode.addChild(joywire); rootNode.addChild(marketta1); rootNode.addChild(overclock); rootNode.addChild(scAssemblers); - baseResearchTree.setRoot(rootNode); + return rootNode; +} + +export function getBaseResearchTreeCopy(): ResearchTree { + const baseResearchTree: ResearchTree = new ResearchTree(); + baseResearchTree.setRoot(createBaseResearchTreeNodes()); return baseResearchTree; } + +// Base Research Tree for Industry's that make products +export function getProductIndustryResearchTreeCopy(): ResearchTree { + const researchTree: ResearchTree = new ResearchTree(); + const root = createBaseResearchTreeNodes(); + + const upgradeFulcrum = makeNode("uPgrade: Fulcrum"); + const upgradeCapacity1 = makeNode("uPgrade: Capacity.I"); + const upgradeCapacity2 = makeNode("uPgrade: Capacity.II"); + const upgradeDashboard = makeNode("uPgrade: Dashboard"); + + upgradeCapacity1.addChild(upgradeCapacity2); + upgradeFulcrum.addChild(upgradeCapacity1); + upgradeFulcrum.addChild(upgradeDashboard); + root.addChild(upgradeFulcrum); + + researchTree.setRoot(root); + + return researchTree; +} diff --git a/src/Corporation/data/ResearchMetadata.ts b/src/Corporation/data/ResearchMetadata.ts index 557766858..ed61a0a97 100644 --- a/src/Corporation/data/ResearchMetadata.ts +++ b/src/Corporation/data/ResearchMetadata.ts @@ -24,6 +24,12 @@ export const researchMetadata: IConstructorParams[] = [ desc: "Research how to automatically administer performance-enhacing drugs to all of " + "your employees. This unlocks Drug-related Research.", }, + { + name: "Bulk Purchasing", + cost: 5e3, + desc: "Research the art of buying materials in bulk. This allows you to purchase " + + "any amount of a material instantly.", + }, { name: "CPH4 Injections", cost: 25e3, @@ -64,7 +70,7 @@ export const researchMetadata: IConstructorParams[] = [ }, { name: "Hi-Tech R&D Laboratory", - cost: 10e3, + cost: 5e3, desc: "Construct a cutting edge facility dedicated to advanced research and " + "and development. This allows you to spend Scientific Research " + "on powerful upgrades. It also globally increases Scientific Research " + @@ -118,7 +124,38 @@ export const researchMetadata: IConstructorParams[] = [ "control confidence and enthusiasm. This research increases the max " + "morale of all employees by 10.", }, - - - + { + name: "sudo.Assist", + cost: 15e3, + desc: "Develop a virtual assistant AI to handle and manage administrative " + + "issues for your corporation.", + }, + { + name: "uPgrade: Capacity.I", + cost: 20e3, + desc: "Expand the industry's capacity for designing and manufacturing its " + + "various products. This increases the industry's maximum number of products " + + "by 1 (from 3 to 4).", + }, + { + name: "uPgrade: Capacity.II", + cost: 30e3, + desc: "Expand the industry's capacity for designing and manufacturing its " + + "various products. This increases the industry's maximum number of products " + + "by 1 (from 4 to 5).", + }, + { + name: "uPgrade: Dashboard", + cost: 5e3, + desc: "Improve the software used to manage the industry's production line " + + "for its various products. This allows you to manage the production and " + + "sale of a product before it's finished being designed.", + }, + { + name: "uPgrade: Fulcrum", + cost: 10e3, + desc: "Streamline the manufacturing of this industry's various products. " + + "This research increases the production of your products by 5%", + productProductionMult: 1.05, + }, ]; diff --git a/src/Corporation/ui/BaseReactComponent.js b/src/Corporation/ui/BaseReactComponent.js new file mode 100644 index 000000000..a6b5d2f9d --- /dev/null +++ b/src/Corporation/ui/BaseReactComponent.js @@ -0,0 +1,22 @@ +// Base class for React Components for Corporation UI +// Contains a few helper functions that let derived classes easily +// access Corporation properties +import React from "react"; + +const Component = React.Component; + +export class BaseReactComponent extends Component { + corp() { + return this.props.corp; + } + + eventHandler() { + return this.props.eventHandler; + } + + routing() { + return this.props.routing; + } + + render() {} +} diff --git a/src/Corporation/ui/CityTabs.jsx b/src/Corporation/ui/CityTabs.jsx new file mode 100644 index 000000000..fd99e8323 --- /dev/null +++ b/src/Corporation/ui/CityTabs.jsx @@ -0,0 +1,56 @@ +// React Components for the Corporation UI's City navigation tabs +// These allow player to navigate between different cities for each industry +import React from "react"; +import { BaseReactComponent } from "./BaseReactComponent"; + +export class CityTabs extends BaseReactComponent { + constructor(props) { + // An object with [key = city name] and [value = click handler] + // needs to be passed into the constructor as the "onClicks" property. + // We'll make sure that that happens here + if (props.onClicks == null) { + throw new Error(`CityTabs component constructed without onClick handlers`); + } + if (props.city == null) { + throw new Error(`CityTabs component constructed without 'city' property`) + } + + super(props); + } + + renderTab(props) { + let className = "cmpy-mgmt-city-tab"; + if (props.current) { + className += " current"; + } + + return ( + + ) + } + + render() { + const tabs = []; + + // Tabs for each city + for (const cityName in this.props.onClicks) { + tabs.push(this.renderTab({ + current: this.props.city === cityName, + key: cityName, + onClick: this.props.onClicks[cityName], + })); + } + + // Tab to "Expand into new City" + const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler()); + tabs.push(this.renderTab({ + current: false, + key: "Expand into new City", + onClick: newCityOnClick, + })); + + return tabs; + } +} diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js new file mode 100644 index 000000000..be7ee277d --- /dev/null +++ b/src/Corporation/ui/CorporationUIEventHandler.js @@ -0,0 +1,1478 @@ +// Creates a class for handling UI events, such as clicks and keyboard events +import { CorporationRouting } from "./Routing"; +import { Corporation, + Industry, + OfficeSpace, + Warehouse, + DividendMaxPercentage, + IssueNewSharesCooldown, + OfficeInitialCost, + OfficeInitialSize, + SellSharesCooldown, + WarehouseInitialCost, + WarehouseInitialSize } from "../Corporation"; + +import { Industries, + IndustryStartingCosts, + IndustryDescriptions, + IndustryResearchTrees } from "../IndustryData"; + +import { Player } from "../../Player"; + +import { numeralWrapper } from "../../ui/numeralFormat"; + +import { dialogBoxCreate } from "../../../utils/DialogBox"; + +import { KEY } from "../../../utils/helpers/keyCodes"; + +import { clearSelector } from "../../../utils/uiHelpers/clearSelector"; +import { createElement } from "../../../utils/uiHelpers/createElement"; +import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement"; +import { createPopup } from "../../../utils/uiHelpers/createPopup"; +import { createPopupCloseButton } from "../../../utils/uiHelpers/createPopupCloseButton"; +import { getSelectText, + getSelectValue } from "../../../utils/uiHelpers/getSelectData"; +import { removeElementById } from "../../../utils/uiHelpers/removeElementById"; + +export class CorporationEventHandler { + constructor(corp, routing) { + if (!(corp instanceof Corporation)) { + throw new Error(`CorporationEventHandler constructed without proper Corporation instance`); + } + if (!(routing instanceof CorporationRouting)) { + throw new Error(`CorporationEventHandler constructed without proper CorporationRouting instance`); + } + + this.corp = corp; + this.routing = routing; + } + + // Create a popup that lets the player bribe factions + // This is created when the player clicks the "Bribe Factions" button in the overview panel + createBribeFactionsPopup() { + const popupId = "cmpy-mgmt-bribe-factions-popup"; + const txt = createElement("p", { + innerText:"You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation" + }); + const factionSelector = createElement("select", { margin:"3px" }); + for (let i = 0; i < Player.factions.length; ++i) { + const facName = Player.factions[i]; + factionSelector.add(createElement("option", { + text: facName, value: facName + })); + } + var repGainText = createElement("p"); + var stockSharesInput; + var moneyInput = createElement("input", { + type:"number", placeholder:"Corporation funds", margin:"5px", + inputListener:()=>{ + var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); + var stockPrice = this.corp.sharePrice; + var stockShares = stockSharesInput.value == null || stockSharesInput.value == "" ? 0 : Math.round(parseFloat(stockSharesInput.value)); + if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { + repGainText.innerText = "ERROR: Invalid value(s) entered"; + } else if (this.corp.funds.lt(money)) { + repGainText.innerText = "ERROR: You do not have this much money to bribe with"; + } else if (this.corp.stockShares > this.corp.numShares) { + repGainText.innerText = "ERROR: You do not have this many shares to bribe with"; + } else { + + var totalAmount = Number(money) + (stockShares * stockPrice); + var repGain = totalAmount / BribeToRepRatio; + repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") + + " reputation with " + + factionSelector.options[factionSelector.selectedIndex].value + + " with this bribe"; + } + } + }); + stockSharesInput = createElement("input", { + type:"number", placeholder:"Stock Shares", margin: "5px", + inputListener:()=>{ + var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); + var stockPrice = this.corp.sharePrice; + var stockShares = stockSharesInput.value == null || stockSharesInput.value == "" ? 0 : Math.round(stockSharesInput.value); + if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { + repGainText.innerText = "ERROR: Invalid value(s) entered"; + } else if (this.corp.funds.lt(money)) { + repGainText.innerText = "ERROR: You do not have this much money to bribe with"; + } else if (this.corp.stockShares > this.corp.numShares) { + repGainText.innerText = "ERROR: You do not have this many shares to bribe with"; + } else { + var totalAmount = money + (stockShares * stockPrice); + var repGain = totalAmount / BribeToRepRatio; + console.log("repGain: " + repGain); + repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") + + " reputation with " + + factionSelector.options[factionSelector.selectedIndex].value + + " with this bribe"; + } + } + }); + var confirmButton = createElement("a", { + class:"a-link-button", innerText:"Bribe", display:"inline-block", + clickListener:()=>{ + var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); + var stockPrice = this.corp.sharePrice; + var stockShares = stockSharesInput.value == null || stockSharesInput.value == ""? 0 : Math.round(parseFloat(stockSharesInput.value)); + var fac = Factions[factionSelector.options[factionSelector.selectedIndex].value]; + if (fac == null) { + dialogBoxCreate("ERROR: You must select a faction to bribe"); + return false; + } + if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { + dialogBoxCreate("ERROR: Invalid value(s) entered"); + } else if (this.corp.funds.lt(money)) { + dialogBoxCreate("ERROR: You do not have this much money to bribe with"); + } else if (stockShares > this.corp.numShares) { + dialogBoxCreate("ERROR: You do not have this many shares to bribe with"); + } else { + var totalAmount = money + (stockShares * stockPrice); + var repGain = totalAmount / BribeToRepRatio; + dialogBoxCreate("You gained " + formatNumber(repGain, 0) + + " reputation with " + fac.name + " by bribing them."); + fac.playerReputation += repGain; + this.corp.funds = this.corp.funds.minus(money); + this.corp.numShares -= stockShares; + removeElementById(popupId); + return false; + } + } + }); + const cancelButton = createPopupCloseButton(popupId, { + display: "inline-block", + innerText: "Cancel", + }) + + createPopup(popupId, [txt, factionSelector, repGainText, + moneyInput, stockSharesInput, confirmButton, cancelButton]); + } + + // Create a popup that lets the player buyback shares + // This is created when the player clicks the "Buyback Shares" button in the overview panel + createBuybackSharesPopup() { + const popupId = "cmpy-mgmt-buyback-shares-popup"; + const currentStockPrice = this.corp.sharePrice; + const buybackPrice = currentStockPrice * 1.1; + const txt = createElement("p", { + innerHTML: "Enter the number of outstanding shares you would like to buy back. " + + "These shares must be bought at a 10% premium. However, " + + "repurchasing shares from the market tends to lead to an increase in stock price.

" + + "To purchase these shares, you must use your own money (NOT your Corporation's funds).

" + + "The current buyback price of your company's stock is " + + numeralWrapper.format(buybackPrice, "$0.000a") + + ". Your company currently has " + numeralWrapper.formatBigNumber(this.corp.issuedShares) + " outstanding stock shares", + }); + var costIndicator = createElement("p", {}); + var input = createElement("input", { + type:"number", placeholder:"Shares to buyback", margin:"5px", + inputListener: ()=> { + var numShares = Math.round(input.value); + //TODO add conditional for if player doesn't have enough money + if (isNaN(numShares) || numShares <= 0) { + costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback" + } else if (numShares > this.corp.issuedShares) { + costIndicator.innerText = "There are not this many shares available to buy back. " + + "There are only " + numeralWrapper.formatBigNumber(this.corp.issuedShares) + + " outstanding shares."; + } else { + costIndicator.innerText = "Purchase " + numShares + " shares for a total of " + + numeralWrapper.format(numShares * buybackPrice, '$0.000a'); + } + } + }); + var confirmBtn = createElement("a", { + class:"a-link-button", innerText:"Buy shares", display:"inline-block", + clickListener: () => { + var shares = Math.round(input.value); + const tempStockPrice = this.corp.sharePrice; + const buybackPrice = tempStockPrice * 1.1; + if (isNaN(shares) || shares <= 0) { + dialogBoxCreate("ERROR: Invalid value for number of shares"); + } else if (shares > this.corp.issuedShares) { + dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back"); + } else if (shares * buybackPrice > Player.money) { + dialogBoxCreate("ERROR: You do not have enough money to purchase this many shares (you need " + + numeralWrapper.format(shares * buybackPrice, "$0.000a") + ")"); + } else { + this.corp.numShares += shares; + if (isNaN(this.corp.issuedShares)) { + console.warn("Corporation issuedShares is NaN: " + this.corp.issuedShares); + console.warn("Converting to number now"); + const res = parseInt(this.corp.issuedShares); + if (isNaN(res)) { + this.corp.issuedShares = 0; + } else { + this.corp.issuedShares = res; + } + } + this.corp.issuedShares -= shares; + Player.loseMoney(shares * buybackPrice); + removeElementById(popupId); + this.rerender(); + } + return false; + + } + }); + var cancelBtn = createPopupCloseButton(popupId, { + class: "std-button", + display: "inline-block", + innerText: "Cancel", + }); + + createPopup(popupId, [txt, costIndicator, input, confirmBtn, cancelBtn]); + input.focus(); + } + + // Create a popup that lets the player discontinue a product + createDiscontinueProductPopup(product) { + const popupId = "cmpy-mgmt-discontinue-product-popup"; + const txt = createElement("p", { + innerText:"Are you sure you want to do this? Discontinuing a product " + + "removes it completely and permanently. You will no longer " + + "produce this product and all of its existing stock will be " + + "removed and left unsold", + }); + const confirmBtn = createElement("a", { + class:"a-link-button",innerText:"Discontinue", + clickListener:()=>{ + industry.discontinueProduct(product, parentRefs); + removeElementById(popupId); + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + + createPopup(popupId, [txt, confirmBtn, cancelBtn]); + } + + // Create a popup that lets the player manage exports + createExportMaterialPopup(mat) { + const corp = this.corp; + + const popupId = "cmpy-mgmt-export-popup"; + const exportTxt = createElement("p", { + innerText:"Select the industry and city to export this material to, as well as " + + "how much of this material to export per second. You can set the export " + + "amount to 'MAX' to export all of the materials in this warehouse." + }); + + //Select industry and city to export to + const citySelector = createElement("select", {class: "dropdown"}); + const industrySelector = createElement("select", { + class: "dropdown", + changeListener: () => { + const industryName = getSelectValue(industrySelector); + for (let i = 0; i < corp.divisions.length; ++i) { + if (corp.divisions[i].name == industryName) { + clearSelector(citySelector); + for (const cityName in corp.divisions[i].warehouses) { + if (corp.divisions[i].warehouses[cityName] instanceof Warehouse) { + citySelector.add(createElement("option", { + value:cityName, text:cityName, + })); + } + } + return; + } + } + } + }); + + for (let i = 0; i < corp.divisions.length; ++i) { + industrySelector.add(createOptionElement(corp.divisions[i].name)); + } + + // Force change listener to initialize citySelector + industrySelector.dispatchEvent(new Event("change")); + + //Select amount to export + const exportAmount = createElement("input", { + class: "text-input", + placeholder:"Export amount / s" + }); + + const exportBtn = createElement("a", { + class: "std-button", display:"inline-block", innerText:"Export", + clickListener: () => { + const industryName = getSelectText(industrySelector); + const cityName = citySelector.options[citySelector.selectedIndex].text; + const amt = exportAmount.value; + + // Sanitize amt + let sanitizedAmt = amt.replace(/\s+/g, ''); + sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, ''); + let temp = sanitizedAmt.replace(/MAX/g, 1); + try { + temp = eval(temp); + } catch(e) { + dialogBoxCreate("Invalid expression entered for export amount: " + e); + return false; + } + + if (temp == null || isNaN(temp)) { + dialogBoxCreate("Invalid amount entered for export"); + return; + } + var exportObj = {ind:industryName, city:cityName, amt:sanitizedAmt}; + mat.exp.push(exportObj); + removeElementById(popupId); + return false; + } + }); + + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + + const currExportsText = createElement("p", { + innerText:"Below is a list of all current exports of this material from this warehouse. " + + "Clicking on one of the exports below will REMOVE that export." + }); + const currExports = []; + for (var i = 0; i < mat.exp.length; ++i) { + (function(i, mat, currExports){ + currExports.push(createElement("div", { + class:"cmpy-mgmt-existing-export", + innerHTML: "Industry: " + mat.exp[i].ind + "
" + + "City: " + mat.exp[i].city + "
" + + "Amount/s: " + mat.exp[i].amt, + clickListener:()=>{ + mat.exp.splice(i, 1); //Remove export object + removeElementById(popupId); + createExportPopup(); + } + })); + })(i, mat, currExports); + } + createPopup(popupId, [exportTxt, industrySelector, citySelector, exportAmount, + exportBtn, cancelBtn, currExportsText].concat(currExports)); + } + + // Create a popup that lets the player issue & manage dividends + // This is created when the player clicks the "Issue Dividends" button in the overview panel + createIssueDividendsPopup() { + const popupId = "cmpy-mgmt-issue-dividends-popup"; + const descText = "Dividends are a distribution of a portion of the corporation's " + + "profits to the shareholders. This includes yourself, as well.

" + + "In order to issue dividends, simply allocate some percentage " + + "of your corporation's profits to dividends. This percentage must be an " + + `integer between 0 and ${DividendMaxPercentage}. (A percentage of 0 means no dividends will be ` + + "issued

" + + "Two important things to note:
" + + " * Issuing dividends will negatively affect your corporation's stock price
" + + " * Dividends are taxed. Taxes start at 50%, but can be decreased

" + + "Example: Assume your corporation makes $100m / sec in profit and you allocate " + + "40% of that towards dividends. That means your corporation will gain $60m / sec " + + "in funds and the remaining $40m / sec will be paid as dividends. Since your " + + "corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share " + + "per second before taxes."; + const txt = createElement("p", { innerHTML: descText }); + + let allocateBtn; + const dividendPercentInput = createElement("input", { + margin: "5px", + placeholder: "Dividend %", + type: "number", + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {allocateBtn.click();} + } + }); + + allocateBtn = createElement("button", { + class: "std-button", + display: "inline-block", + innerText: "Allocate Dividend Percentage", + clickListener: () => { + const percentage = Math.round(parseInt(dividendPercentInput.value)); + if (isNaN(percentage) || percentage < 0 || percentage > DividendMaxPercentage) { + return dialogBoxCreate(`Invalid value. Must be an integer between 0 and ${DividendMaxPercentage}`); + } + + this.corp.dividendPercentage = percentage; + + removeElementById(popupId); + + this.rerender(); + return false; + } + }); + + const cancelBtn = createPopupCloseButton(popupId, { + class: "std-button", + display: "inline-block", + innerText: "Cancel", + }); + + createPopup(popupId, [txt, dividendPercentInput, allocateBtn, cancelBtn]); + dividendPercentInput.focus(); + } + + // Create a popup that lets the player issue new shares + // This is created when the player clicks the "Issue New Shares" buttons in the overview panel + createIssueNewSharesPopup() { + const popupId = "cmpy-mgmt-issue-new-shares-popup"; + const maxNewSharesUnrounded = Math.round(this.corp.totalShares * 0.2); + const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); + + const descText = createElement("p", { + innerHTML: "You can issue new equity shares (i.e. stocks) in order to raise " + + "capital for your corporation.

" + + ` * You can issue at most ${numeralWrapper.format(maxNewShares, "0.000a")} new shares
` + + ` * New shares are sold at a 10% discount
` + + ` * You can only issue new shares once every 12 hours
` + + ` * Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share
` + + ` * Number of new shares issued must be a multiple of 10 million

` + + `When you choose to issue new equity, private shareholders have first priority for up to 50% of the new shares. ` + + `If they choose to exercise this option, these newly issued shares become private, restricted shares, which means ` + + `you cannot buy them back.`, + }); + + let issueBtn, newSharesInput; + const dynamicText = createElement("p", { + display: "block", + }); + + function updateDynamicText(corp) { + const newSharePrice = Math.round(corp.sharePrice * 0.9); + let newShares = parseInt(newSharesInput.value); + if (isNaN(newShares)) { + dynamicText.innerText = "Invalid input"; + return; + } + + // Round to nearest ten-millionth + newShares /= 10e6; + newShares = Math.round(newShares) * 10e6; + + if (newShares < 10e6) { + dynamicText.innerText = "Must issue at least 10 million new shares"; + return; + } + + if (newShares > maxNewShares) { + dynamicText.innerText = "You cannot issue that many shares"; + return; + } + + dynamicText.innerText = `Issue ${numeralWrapper.format(newShares, "0.000a")} new shares ` + + `for ${numeralWrapper.formatMoney(newShares * newSharePrice)}?` + } + newSharesInput = createElement("input", { + margin: "5px", + placeholder: "# New Shares", + type: "number", + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) { + issueBtn.click(); + } else { + updateDynamicText(this.corp); + } + } + }); + + issueBtn = createElement("a", { + class: "std-button", + display: "inline-block", + innerText: "Issue New Shares", + clickListener: () => { + const newSharePrice = Math.round(this.corp.sharePrice * 0.9); + let newShares = parseInt(newSharesInput.value); + if (isNaN(newShares)) { + dialogBoxCreate("Invalid input for number of new shares"); + return; + } + + // Round to nearest ten-millionth + newShares = Math.round(newShares / 10e6) * 10e6; + + if (newShares < 10e6 || newShares > maxNewShares) { + dialogBoxCreate("Invalid input for number of new shares"); + return; + } + + const profit = newShares * newSharePrice; + this.corp.issueNewSharesCooldown = IssueNewSharesCooldown; + this.corp.totalShares += newShares; + + // Determine how many are bought by private investors + // Private investors get up to 50% at most + // Round # of private shares to the nearest millionth + let privateShares = getRandomInt(0, Math.round(newShares / 2)); + privateShares = Math.round(privateShares / 1e6) * 1e6; + + this.corp.issuedShares += (newShares - privateShares); + this.corp.funds = this.corp.funds.plus(profit); + this.corp.immediatelyUpdateSharePrice(); + + removeElementById(popupId); + dialogBoxCreate(`Issued ${numeralWrapper.format(newShares, "0.000a")} and raised ` + + `${numeralWrapper.formatMoney(profit)}. ${numeralWrapper.format(privateShares, "0.000a")} ` + + `of these shares were bought by private investors.

` + + `Stock price decreased to ${numeralWrapper.formatMoney(this.corp.sharePrice)}`); + + this.rerender(); + return false; + } + }); + + const cancelBtn = createPopupCloseButton(popupId, { + class: "std-button", + display: "inline-block", + innerText: "Cancel", + }); + + createPopup(popupId, [descText, dynamicText, newSharesInput, issueBtn, cancelBtn]); + newSharesInput.focus(); + } + + // Create a popup that lets the player limit the production of a product + createLimitProductProdutionPopup(product) { + const popupId = "cmpy-mgmt-limit-product-production-popup"; + const txt = createElement("p", { + innerText:"Enter a limit to the amount of this product you would " + + "like to product per second. Leave the box empty to set no limit." + }); + let confirmBtn; + const input = createElement("input", { + type:"number", placeholder:"Limit", + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) { confirmBtn.click(); } + } + }); + confirmBtn = createElement("a", { + class: "std-button", + display:"inline-block", + innerText:"Limit production", + margin:'6px', + clickListener: () => { + if (input.value === "") { + product.prdman[city][0] = false; + removeElementById(popupId); + return false; + } + var qty = parseFloat(input.value); + if (isNaN(qty)) { + dialogBoxCreate("Invalid value entered"); + return false; + } + if (qty < 0) { + product.prdman[city][0] = false; + } else { + product.prdman[city][0] = true; + product.prdman[city][1] = qty; + } + removeElementById(popupId); + this.rerender(); + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + cancelBtn.style.margin = "6px"; + + createPopup(popupId, [txt, input, confirmBtn, cancelBtn]); + input.focus(); + } + + // Create a popup that lets the player create a product for their current industry + createMakeProductPopup(popupText, division) { + if (division.hasMaximumNumberProducts()) { return; } + + const popupId = "cmpy-mgmt-create-product-popup"; + const txt = createElement("p", { + innerHTML: popupText, + }); + const designCity = createElement("select"); + for (const cityName in division.offices) { + if (division.offices[cityName] instanceof OfficeSpace) { + designCity.add(createElement("option", { + value: cityName, + text: cityName + })); + } + } + let productNamePlaceholder = "Product Name"; + if (division.type === Industries.Food) { + productNamePlaceholder = "Restaurant Name"; + } else if (division.type === Industries.Healthcare) { + productNamePlaceholder = "Hospital Name"; + } else if (division.type === Industries.RealEstate) { + productNamePlaceholder = "Property Name"; + } + var productNameInput = createElement("input", { + placeholder: productNamePlaceholder, + }); + var lineBreak1 = createElement("br"); + var designInvestInput = createElement("input", { + type: "number", + placeholder: "Design investment" + }); + var marketingInvestInput = createElement("input", { + type: "number", + placeholder: "Marketing investment" + }); + const confirmBtn = createElement("a", { + class: "std-button", + innerText: "Develop Product", + clickListener: () => { + if (designInvestInput.value == null) { designInvestInput.value = 0; } + if (marketingInvestInput.value == null) { marketingInvestInput.value = 0; } + var designInvest = parseFloat(designInvestInput.value), + marketingInvest = parseFloat(marketingInvestInput.value); + if (productNameInput.value == null || productNameInput.value === "") { + dialogBoxCreate("You must specify a name for your product!"); + } else if (isNaN(designInvest)) { + dialogBoxCreate("Invalid value for design investment"); + } else if (isNaN(marketingInvest)) { + dialogBoxCreate("Invalid value for marketing investment"); + } else if (this.corp.funds.lt(designInvest + marketingInvest)) { + dialogBoxCreate("You don't have enough company funds to make this large of an investment"); + } else { + const product = new Product({ + name:productNameInput.value.replace(/[<>]/g, ''), //Sanitize for HTMl elements + createCity:designCity.options[designCity.selectedIndex].value, + designCost: designInvest, + advCost: marketingInvest, + }); + this.corp.funds = this.corp.funds.minus(designInvest + marketingInvest); + division.products[product.name] = product; + removeElementById(popupId); + } + this.rerender(); + //this.updateUIContent(); + //this.displayDivisionContent(division, city); + return false; + } + }) + const cancelBtn = createPopupCloseButton(popupid, { + class: "std-button", + innerText: "Cancel", + }); + + createPopup(popupId, [txt, designCity, productNameInput, lineBreak1, + designInvestInput, marketingInvestInput, confirmBtn, cancelBtn]); + productNameInput.focus(); + } + + // Create a popup that lets the player use the Market TA research + createMarketTaPopup(mat, industry) { + const corp = this.corp; + + const popupId = "cmpy-mgmt-marketta-popup"; + const markupLimit = mat.getMarkupLimit(); + const ta1 = createElement("p", { + innerHTML: "Market-TA.I
" + + "The maximum sale price you can mark this up to is " + + numeralWrapper.formatMoney(mat.bCost + markupLimit) + + ". This means that if you set the sale price higher than this, " + + "you will begin to experience a loss in number of sales", + }); + const closeBtn = createPopupCloseButton(popupId, { + class: "std-button", + display: "block", + }); + + if (industry.hasResearch("Market-TA.II")) { + let updateTa2Text; + const ta2Text = createElement("p"); + const ta2Input = createElement("input", { + marginTop: "4px", + onkeyup: (e) => { + e.preventDefault(); + updateTa2Text(); + }, + type: "number", + value: mat.bCost, + }); + + // Function that updates the text in ta2Text element + updateTa2Text = function() { + const sCost = parseFloat(ta2Input.value); + let markup = 1; + if (sCost > mat.bCost) { + //Penalty if difference between sCost and bCost is greater than markup limit + if ((sCost - mat.bCost) > markupLimit) { + markup = Math.pow(markupLimit / (sCost - mat.bCost), 2); + } + } else if (sCost < mat.bCost) { + if (sCost <= 0) { + markup = 1e12; //Sell everything, essentially discard + } else { + //Lower prices than market increases sales + markup = mat.bCost / sCost; + } + } + ta2Text.innerHTML = `
Market-TA.II
` + + `If you sell at ${numeralWrapper.formatMoney(sCost)}, ` + + `then you will sell ${formatNumber(markup, 5)}x as much compared ` + + `to if you sold at market price.`; + } + updateTa2Text(); + createPopup(popupId, [ta1, ta2Text, ta2Input, closeBtn]); + } else { + // Market-TA.I only + createPopup(popupId, [ta1, closeBtn]); + } + } + + // Create a popup that lets the player expand into a new city (for the current industry) + createNewCityPopup(division) { + const popupId = "cmpy-mgmt-expand-city-popup"; + const text = createElement("p", { + innerText: "Would you like to expand into a new city by opening an office? " + + "This would cost " + numeralWrapper.format(OfficeInitialCost, '$0.000a'), + }); + const citySelector = createElement("select", { class: "dropdown", margin:"5px" }); + for (const cityName in division.offices) { + if (division.offices.hasOwnProperty(cityName)) { + if (!(division.offices[cityName] instanceof OfficeSpace)) { + citySelector.add(createElement("option", { + text: cityName, + value: cityName + })); + } + } + } + + const confirmBtn = createElement("a", { + class:"std-button", + display:"inline-block", + innerText: "Confirm", + margin:"3px", + clickListener: () => { + let city = citySelector.options[citySelector.selectedIndex].value; + if (this.corp.funds.lt(OfficeInitialCost)) { + dialogBoxCreate("You don't have enough company funds to open a new office!"); + } else { + this.corp.funds = this.corp.funds.minus(OfficeInitialCost); + dialogBoxCreate("Opened a new office in " + city + "!"); + division.offices[city] = new OfficeSpace({ + loc: city, + size: OfficeInitialSize, + }); + this.corp.displayDivisionContent(division, city); + } + removeElementById(popupId); + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { + class: "std-button", + innerText: "Cancel", + }); + + createPopup(popupId, [text, citySelector, confirmBtn, cancelBtn]); + } + + // Create a popup that lets the player create a new industry. + // This is created when the player clicks the "Expand into new Industry" header tab + createNewIndustryPopup() { + const popupId = "cmpy-mgmt-expand-industry-popup"; + if (document.getElementById(popupId) != null) { return; } + + var txt = createElement("p", { + innerHTML: "Create a new division to expand into a new industry:", + }); + var selector = createElement("select", { + class:"dropdown" + }); + var industryDescription = createElement("p", {}); + var yesBtn; + var nameInput = createElement("input", { + type:"text", + id:"cmpy-mgmt-expand-industry-name-input", + class: "text-input", + display:"block", + maxLength: 30, + pattern:"[a-zA-Z0-9-_]", + onkeyup:(e)=>{ + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {yesBtn.click();} + } + }); + var nameLabel = createElement("label", { + for:"cmpy-mgmt-expand-industry-name-input", + innerText:"Division name: " + }); + yesBtn = createElement("span", { + class:"popup-box-button", + innerText:"Create Division", + clickListener: ()=>{ + const ind = selector.options[selector.selectedIndex].value; + const newDivisionName = nameInput.value; + + for (let i = 0; i < this.corp.divisions.length; ++i) { + if (this.corp.divisions[i].name === newDivisionName) { + dialogBoxCreate("This name is already in use!"); + return false; + } + } + if (this.corp.funds.lt(IndustryStartingCosts[ind])) { + dialogBoxCreate("Not enough money to create a new division in this industry"); + } else if (newDivisionName === "") { + dialogBoxCreate("New division must have a name!"); + } else { + this.corp.funds = this.corp.funds.minus(IndustryStartingCosts[ind]); + var newInd = new Industry({ + name:newDivisionName, + type:ind, + }); + this.corp.divisions.push(newInd); + // this.corp.updateUIHeaderTabs(); + // this.corp.selectHeaderTab(headerTabs[headerTabs.length-2]); + removeElementById("cmpy-mgmt-expand-industry-popup"); + this.rerender(); + // this.corp.displayDivisionContent(newInd, Locations.Sector12); + } + return false; + } + }); + + const noBtn = createPopupCloseButton(popupId, { + display: "inline-block", + innerText: "Cancel", + }); + + //Make an object to keep track of what industries you're already in + const ownedIndustries = {}; + for (let i = 0; i < this.corp.divisions.length; ++i) { + ownedIndustries[this.corp.divisions[i].type] = true; + } + + //Add industry types to selector + //Have Agriculture be first as recommended option + if (!ownedIndustries["Agriculture"]) { + selector.add(createElement("option", { + text:Industries["Agriculture"], value:"Agriculture" + })); + } + + for (var key in Industries) { + if (key !== "Agriculture" && Industries.hasOwnProperty(key) && !ownedIndustries[key]) { + var ind = Industries[key]; + selector.add(createElement("option", { + text: ind,value:key, + })); + } + } + + //Initial Industry Description + var ind = selector.options[selector.selectedIndex].value; + industryDescription.innerHTML = (IndustryDescriptions[ind] + "

"); + + //Change the industry description text based on selected option + selector.addEventListener("change", function() { + var ind = selector.options[selector.selectedIndex].value; + industryDescription.innerHTML = IndustryDescriptions[ind] + "

"; + }); + + //Add to DOM + const elems = []; + elems.push(txt); + elems.push(selector); + elems.push(industryDescription); + elems.push(nameLabel); + elems.push(nameInput); + elems.push(noBtn); + elems.push(yesBtn); + + createPopup(popupId, elems); + nameInput.focus(); + + return false; + } + + // Create a popup that lets the player purchase a Material + createPurchaseMaterialPopup(mat, industry) { + const corp = this.corp; + + const purchasePopupId = "cmpy-mgmt-material-purchase-popup"; + const txt = createElement("p", { + innerHTML: "Enter the amount of " + mat.name + " you would like " + + "to purchase per second. This material's cost changes constantly" + }); + let confirmBtn; + let input = createElement("input", { + margin: "5px", + placeholder: "Purchase amount", + type: "number", + value: mat.buy ? mat.buy : null, + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + confirmBtn = createElement("button", { + innerText: "Confirm", class: "std-button", + clickListener: () => { + if (isNaN(input.value)) { + dialogBoxCreate("Invalid amount"); + } else { + mat.buy = parseFloat(input.value); + if (isNaN(mat.buy)) {mat.buy = 0;} + removeElementById(purchasePopupId); + return false; + } + } + }); + const clearButton = createElement("button", { + innerText: "Clear Purchase", class: "std-button", + clickListener: () => { + mat.buy = 0; + removeElementById(purchasePopupId); + return false; + } + }); + const cancelBtn = createPopupCloseButton(purchasePopupId, { + class: "std-button", + innerText: "Cancel", + }); + + const elems = [txt, input, confirmBtn, clearButton, cancelBtn]; + + if (industry.hasResearch("Bulk Purchasing")) { + const bulkPurchaseInfo = createElement("p", { + innerText: "Enter the amount of " + mat.name + " you would like " + + "to bulk purchase. This purchases the specified amount instantly " + + "(all at once).", + }); + + let bulkPurchaseCostTxt = createElement("p"); + function updateBulkPurchaseText(amount) { + const cost = parseFloat(amount) * mat.bCost; + if (isNaN(cost)) { + dialogBoxCreate(`Bulk Purchase Cost calculated to be NaN. This is either due to ` + + `invalid input, or it is a bug (in which case you should report to dev)`); + return; + } + + bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(amt, "0,0.00")} of ` + + `${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`; + } + + let bulkPurchaseConfirmBtn; + const bulkPurchaseInput = createElement("input", { + margin: "5px", + placeholder: "Bulk Purchase amount", + type: "number", + onkeyup: (e) => { + e.preventDefault(); + bulkPurchaseUpdateCostTxt(); + if (e.keyCode === KEY.ENTER) {bulkPurchaseConfirmBtn.click();} + } + }); + + bulkPurchaseConfirmBtn = createElement("button", { + class: "std-button", + innerText: "Confirm Bulk Purchase", + clickListener: () => { + const amount = parseFloat(input.value); + if (isNaN(amount)) { + dialogBoxCreate("Invalid input amount"); + } else { + const cost = amount * mat.bCost; + if (corp.funds.gt(cost)) { + corp.funds = corp.funds.minus(cost); + mat.qty += amount; + } else { + dialogBoxCreate(`You cannot afford this purchase.`); + return false; + } + + removeElementById(purchasePopupId); + return false; + } + } + }) + + elems.push(bulkPurchaseInfo); + elems.push(bulkPurchaseCostTxt); + elems.push(bulkPurchaseInput); + } + + createPopup(purchasePopupId, elems); + input.focus(); + } + + // Create a popup that let the player manage sales of a material + createSellMaterialPopup(mat) { + const sellPopupId = "cmpy-mgmt-material-sell-popup"; + const txt = createElement("p", { + innerHTML: "Enter the maximum amount of " + mat.name + " you would like " + + "to sell per second, as well as the price at which you would " + + "like to sell at.

" + + "If the sell amount is set to 0, then the material will not be sold. If the sell price " + + "if set to 0, then the material will be discarded

" + + "Setting the sell amount to 'MAX' will result in you always selling the " + + "maximum possible amount of the material.

" + + "When setting the sell amount, you can use the 'PROD' variable to designate a dynamically " + + "changing amount that depends on your production. For example, if you set the sell amount " + + "to 'PROD-5' then you will always sell 5 less of the material than you produce.

" + + "When setting the sell price, you can use the 'MP' variable to designate a dynamically " + + "changing price that depends on the market price. For example, if you set the sell price " + + "to 'MP+10' then it will always be sold at $10 above the market price.", + }); + const br = createElement("br"); + let confirmBtn; + const inputQty = createElement("input", { + type: "text", marginTop: "4px", + value: mat.sllman[1] ? mat.sllman[1] : null, + placeholder: "Sell amount", + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + const inputPx = createElement("input", { + type: "text", marginTop: "4px", + value: mat.sCost ? mat.sCost : null, placeholder: "Sell price", + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + confirmBtn = createElement("button", { + class: "std-button", + innerText: "Confirm", + clickListener: () => { + //Parse price + let cost = inputPx.value.replace(/\s+/g, ''); + cost = cost.replace(/[^-()\d/*+.MP]/g, ''); //Sanitize cost + let temp = cost.replace(/MP/g, mat.bCost); + try { + temp = eval(temp); + } catch(e) { + dialogBoxCreate("Invalid value or expression for sell price field: " + e); + return false; + } + + if (temp == null || isNaN(temp)) { + dialogBoxCreate("Invalid value or expression for sell price field"); + return false; + } + + if (cost.includes("MP")) { + mat.sCost = cost; //Dynamically evaluated + } else { + mat.sCost = temp; + } + + //Parse quantity + if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) { + let qty = inputQty.value.replace(/\s+/g, ''); + qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); + let tempQty = qty.replace(/MAX/g, 1); + tempQty = tempQty.replace(/PROD/g, 1); + try { + tempQty = eval(tempQty); + } catch(e) { + dialogBoxCreate("Invalid value or expression for sell price field: " + e); + return false; + } + + if (tempQty == null || isNaN(tempQty)) { + dialogBoxCreate("Invalid value or expression for sell price field"); + return false; + } + + mat.sllman[0] = true; + mat.sllman[1] = qty; //Use sanitized input + } else if (isNaN(inputQty.value)) { + dialogBoxCreate("Invalid value for sell quantity field! Must be numeric or 'MAX'"); + return false; + } else { + var qty = parseFloat(inputQty.value); + if (isNaN(qty)) {qty = 0;} + if (qty === 0) { + mat.sllman[0] = false; + mat.sllman[1] = 0; + } else { + mat.sllman[0] = true; + mat.sllman[1] = qty; + } + } + + removeElementById(sellPopupId); + this.rerender(); + return false; + } + }); + const cancelBtn = createPopupCloseButton(sellPopupId, { + class: "std-button", + innerText: "Cancel", + }); + + createPopup(sellPopupId, [txt, br, inputQty, inputPx, confirmBtn, cancelBtn]); + inputQty.focus(); + } + + // Create a popup that lets the player manage sales of the product + createSellProductPopup(product) { + const popupId = "cmpy-mgmt-sell-product-popup"; + const txt = createElement("p", { + innerHTML:"Enter the maximum amount of " + product.name + " you would like " + + "to sell per second, as well as the price at which you would like to " + + "sell it at.

" + + "If the sell amount is set to 0, then the product will not be sold. If the " + + "sell price is set to 0, then the product will be discarded.

" + + "Setting the sell amount to 'MAX' will result in you always selling the " + + "maximum possible amount of the material.

" + + "When setting the sell amount, you can use the 'PROD' variable to designate a " + + "dynamically changing amount that depends on your production. For example, " + + "if you set the sell amount to 'PROD-1' then you will always sell 1 less of " + + "the material than you produce.

" + + "When setting the sell price, you can use the 'MP' variable to set a " + + "dynamically changing price that depends on the Product's estimated " + + "market price. For example, if you set it to 'MP*5' then it " + + "will always be sold at five times the estimated market price.", + }); + let confirmBtn; + const inputQty = createElement("input", { + placeholder: "Sell amount", + type: "text", + value:product.sllman[city][1] ? product.sllman[city][1] : null, + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + const inputPx = createElement("input", { + placeholder: "Sell price", + type: "text", + value: product.sCost ? product.sCost : null, + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + confirmBtn = createElement("a", { + class: "std-button", + innerText: "Confirm", + clickListener: () => { + //Parse price + if (inputPx.value.includes("MP")) { + //Dynamically evaluated quantity. First test to make sure its valid + //Sanitize input, then replace dynamic variables with arbitrary numbers + var price = inputPx.value.replace(/\s+/g, ''); + price = price.replace(/[^-()\d/*+.MP]/g, ''); + var temp = price.replace(/MP/g, 1); + try { + temp = eval(temp); + } catch(e) { + dialogBoxCreate("Invalid value or expression for sell quantity field: " + e); + return false; + } + if (temp == null || isNaN(temp)) { + dialogBoxCreate("Invalid value or expression for sell quantity field."); + return false; + } + product.sCost = price; //Use sanitized price + } else { + var cost = parseFloat(inputPx.value); + if (isNaN(cost)) { + dialogBoxCreate("Invalid value for sell price field"); + return false; + } + product.sCost = cost; + } + + //Parse quantity + if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) { + //Dynamically evaluated quantity. First test to make sure its valid + var qty = inputQty.value.replace(/\s+/g, ''); + qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); + var temp = qty.replace(/MAX/g, 1); + temp = temp.replace(/PROD/g, 1); + try { + temp = eval(temp); + } catch(e) { + dialogBoxCreate("Invalid value or expression for sell price field: " + e); + return false; + } + + if (temp == null || isNaN(temp)) { + dialogBoxCreate("Invalid value or expression for sell price field"); + return false; + } + product.sllman[city][0] = true; + product.sllman[city][1] = qty; //Use sanitized input + } else if (isNaN(inputQty.value)) { + dialogBoxCreate("Invalid value for sell quantity field! Must be numeric"); + return false; + } else { + var qty = parseFloat(inputQty.value); + if (isNaN(qty)) {qty = 0;} + if (qty === 0) { + product.sllman[city][0] = false; + } else { + product.sllman[city][0] = true; + product.sllman[city][1] = qty; + } + } + + removeElementById(popupId); + this.rerender(); + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + + createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn]); + inputQty.focus(); + } + + // Create a popup that lets the player sell Corporation shares + // This is created when the player clicks the "Sell Shares" button in the overview panel + createSellSharesPopup() { + const popupId = "cmpy-mgmt-sell-shares-popup"; + const currentStockPrice = this.corp.sharePrice; + const txt = createElement("p", { + innerHTML: "Enter the number of shares you would like to sell. The money from " + + "selling your shares will go directly to you (NOT your Corporation).

" + + "Selling your shares will cause your corporation's stock price to fall due to " + + "dilution. Furthermore, selling a large number of shares all at once will have an immediate effect " + + "in reducing your stock price.

" + + "The current price of your " + + "company's stock is " + numeralWrapper.format(currentStockPrice, "$0.000a"), + }); + const profitIndicator = createElement("p"); + const input = createElement("input", { + type:"number", placeholder:"Shares to sell", margin:"5px", + inputListener: ()=> { + var numShares = Math.round(input.value); + if (isNaN(numShares) || numShares <= 0) { + profitIndicator.innerText = "ERROR: Invalid value entered for number of shares to sell" + } else if (numShares > this.corp.numShares) { + profitIndicator.innerText = "You don't have this many shares to sell!"; + } else { + const stockSaleResults = this.corp.calculateShareSale(numShares); + const profit = stockSaleResults[0]; + const newSharePrice = stockSaleResults[1]; + const newSharesUntilUpdate = stockSaleResults[2]; + profitIndicator.innerText = "Sell " + numShares + " shares for a total of " + + numeralWrapper.format(profit, '$0.000a'); + } + } + }); + const confirmBtn = createElement("a", { + class:"a-link-button", innerText:"Sell shares", display:"inline-block", + clickListener:()=>{ + var shares = Math.round(input.value); + if (isNaN(shares) || shares <= 0) { + dialogBoxCreate("ERROR: Invalid value for number of shares"); + } else if (shares > this.corp.numShares) { + dialogBoxCreate("ERROR: You don't have this many shares to sell"); + } else { + const stockSaleResults = this.corp.calculateShareSale(shares); + const profit = stockSaleResults[0]; + const newSharePrice = stockSaleResults[1]; + const newSharesUntilUpdate = stockSaleResults[2]; + + this.corp.numShares -= shares; + if (isNaN(this.corp.issuedShares)) { + console.log("ERROR: Corporation issuedShares is NaN: " + this.corp.issuedShares); + console.log("Converting to number now"); + var res = parseInt(this.corp.issuedShares); + if (isNaN(res)) { + this.corp.issuedShares = 0; + } else { + this.corp.issuedShares = res; + } + } + this.corp.issuedShares += shares; + this.corp.sharePrice = newSharePrice; + this.corp.shareSalesUntilPriceUpdate = newSharesUntilUpdate; + this.corp.shareSaleCooldown = SellSharesCooldown; + Player.gainMoney(profit); + Player.recordMoneySource(profit, "corporation"); + removeElementById(popupId); + dialogBoxCreate(`Sold ${numeralWrapper.formatMoney(shares, "0.000a")} shares for ` + + `${numeralWrapper.formatMoney(profit, "$0.000a")}. ` + + `The corporation's stock price fell to ${numeralWrapper.formatMoney(this.corp.sharePrice)} ` + + `as a result of dilution.`); + + this.rerender(); + return false; + } + + } + }); + const cancelBtn = createPopupCloseButton(popupId, { + class: "std-button", + display: "inline-block", + innerText: "Cancel", + }); + + createPopup(popupId, [txt, profitIndicator, input, confirmBtn, cancelBtn]); + input.focus(); + } + + // Creates a popup that lets the player throw an office party + createThrowOfficePartyPopup(office) { + const popupId = "cmpy-mgmt-throw-office-party-popup"; + const txt = createElement("p", { + innerText:"Enter the amount of money you would like to spend PER EMPLOYEE " + + "on this office party" + }); + const totalCostTxt = createElement("p", { + innerText:"Throwing this party will cost a total of $0" + }); + let confirmBtn; + const input = createElement("input", { + type: "number", margin: "5px", placeholder: "$ / employee", + inputListener: () => { + if (isNaN(input.value) || input.value < 0) { + totalCostTxt.innerText = "Invalid value entered!" + } else { + const totalCost = input.value * office.employees.length; + totalCostTxt.innerText = "Throwing this party will cost a total of " + numeralWrapper.format(totalCost, '$0.000a'); + } + }, + onkeyup:(e)=>{ + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + confirmBtn = createElement("a", { + class: "std-button", + innerText: "Throw Party", + clickListener:()=>{ + if (isNaN(input.value) || input.value < 0) { + dialogBoxCreate("Invalid value entered"); + } else { + var totalCost = input.value * office.employees.length; + if (this.corp.funds.lt(totalCost)) { + dialogBoxCreate("You don't have enough company funds to throw this.corp party!"); + } else { + this.corp.funds = this.funds.minus(totalCost); + var mult; + for (let fooit = 0; fooit < office.employees.length; ++fooit) { + mult = office.employees[fooit].throwParty(input.value); + } + dialogBoxCreate("You threw a party for the office! The morale and happiness " + + "of each employee increased by " + formatNumber((mult-1) * 100, 2) + "%."); + removeElementById(popupId); + } + } + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + + createPopup(popupId, [txt, totalCostTxt, input, confirmBtn, cancelBtn]); + input.focus(); + } + + // Creates a popup that lets the player upgrade the current OfficeSpace's size + createUpgradeOfficeSizePopup(office) { + const popupId = "cmpy-mgmt-upgrade-office-size-popup"; + const initialPriceMult = Math.round(office.size / OfficeInitialSize); + const costMultiplier = 1.09; + const upgradeCost = OfficeInitialCost * Math.pow(costMultiplier, initialPriceMult); + + // Calculate cost to upgrade size by 15 employees + let mult = 0; + for (let i = 0; i < 5; ++i) { + mult += (Math.pow(costMultiplier, initialPriceMult + i)); + } + const upgradeCost15 = OfficeInitialCost * mult; + + //Calculate max upgrade size and cost + let maxMult = (this.corp.funds.dividedBy(OfficeInitialCost)).toNumber(); + let maxNum = 1; + mult = Math.pow(costMultiplier, initialPriceMult); + while(maxNum < 50) { //Hard cap of 50x (extra 150 employees) + if (mult >= maxMult) {break;} + let multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum); + if (mult + multIncrease > maxMult) { + break; + } else { + mult += multIncrease; + } + ++maxNum; + } + const upgradeCostMax = OfficeInitialCost * mult; + + const text = createElement("p", { + innerText:"Increase the size of your office space to fit additional employees!" + }); + const text2 = createElement("p", { innerText: "Upgrade size: " }); + + const confirmBtn = createElement("a", { + class: this.corp.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button", + display:"inline-block", margin:"4px", innerText:"by 3", + tooltip:numeralWrapper.format(upgradeCost, "$0.000a"), + clickListener:()=>{ + if (this.corp.funds.lt(upgradeCost)) { + dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); + } else { + office.size += OfficeInitialSize; + this.corp.funds = this.corp.funds.minus(upgradeCost); + dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); + this.rerender(); + } + removeElementById(popupId); + return false; + } + }); + const confirmBtn15 = createElement("a", { + class: this.corp.funds.lt(upgradeCost15) ? "a-link-button-inactive" : "a-link-button", + display:"inline-block", margin:"4px", innerText:"by 15", + tooltip:numeralWrapper.format(upgradeCost15, "$0.000a"), + clickListener:()=>{ + if (this.corp.funds.lt(upgradeCost15)) { + dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); + } else { + office.size += (OfficeInitialSize * 5); + this.corp.funds = this.corp.funds.minus(upgradeCost15); + dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); + this.rerender(); + } + removeElementById(popupId); + return false; + } + }); + const confirmBtnMax = createElement("a", { + class:this.corp.funds.lt(upgradeCostMax) ? "a-link-button-inactive" : "a-link-button", + display:"inline-block", margin:"4px", innerText:"by MAX (" + maxNum*OfficeInitialSize + ")", + tooltip:numeralWrapper.format(upgradeCostMax, "$0.000a"), + clickListener:()=>{ + if (this.corp.funds.lt(upgradeCostMax)) { + dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); + } else { + office.size += (OfficeInitialSize * maxNum); + this.corp.funds = this.corp.funds.minus(upgradeCostMax); + dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); + this.rerender(); + } + removeElementById(popupId); + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + cancelBtn.style.margin = "4px"; + + createPopup(popupId, [text, text2, confirmBtn, confirmBtn15, confirmBtnMax, cancelBtn]); + } + + // Purchases a new Warehouse + purchaseWarehouse(division, city) { + const corp = this.corp; + if (corp.funds.lt(WarehouseInitialCost)) { + dialogBoxCreate("You do not have enough funds to do this!"); + } else { + division.warehouses[city] = new Warehouse({ + loc: city, + size: WarehouseInitialSize, + }); + corp.funds = corp.funds.minus(WarehouseInitialCost); + this.rerender(); + } + } + + rerender() { + this.corp.rerender(); + } +} diff --git a/src/Corporation/ui/HeaderTabs.jsx b/src/Corporation/ui/HeaderTabs.jsx new file mode 100644 index 000000000..a0e6fd1da --- /dev/null +++ b/src/Corporation/ui/HeaderTabs.jsx @@ -0,0 +1,80 @@ +// React Components for the Corporation UI's navigation tabs +// These are the tabs at the top of the UI that let you switch to different +// divisions, see an overview of your corporation, or create a new industry +import React from "react"; +import { BaseReactComponent } from "./BaseReactComponent"; + +import { overviewPage } from "./Routing"; + +function HeaderTab(props) { + let className = "cmpy-mgmt-header-tab"; + if (props.current) { + className += " current"; + } + + return ( + + ) +} + +export class HeaderTabs extends BaseReactComponent { + renderTab(props) { + return ( + + ) + } + + render() { + const overviewOnClick = () => { + this.routing().routeToOverviewPage(); + this.corp().rerender(); + } + + const divisionOnClicks = {}; + for (const division of this.corp().divisions) { + const name = division.name; + const onClick = () => { + this.routing().routeTo(name); + this.corp().rerender(); + } + + divisionOnClicks[name] = onClick; + } + + return ( +
+ { + this.renderTab({ + current: this.routing().isOnOverviewPage(), + key: "overview", + onClick: overviewOnClick, + text: this.corp().name, + }) + } + { + this.corp().divisions.map((division) => { + return this.renderTab({ + current: this.routing().isOn(division.name), + key: division.name, + onClick: divisionOnClicks[division.name], + text: division.name, + }); + }) + } + { + this.renderTab({ + onClick: this.eventHandler().createNewIndustryPopup.bind(this.eventHandler()), + text: "Expand into new Industry" + }) + } +
+ ) + } +} diff --git a/src/Corporation/ui/Industry.jsx b/src/Corporation/ui/Industry.jsx new file mode 100644 index 000000000..c6787e3ed --- /dev/null +++ b/src/Corporation/ui/Industry.jsx @@ -0,0 +1,34 @@ +// React Component for managing the Corporation's Industry UI +// This Industry component does NOT include the city tabs at the top +import React from "react"; +import { BaseReactComponent } from "./BaseReactComponent"; + +import { IndustryOffice } from "./IndustryOffice"; +import { IndustryOverview } from "./IndustryOverview"; +import { IndustryWarehouse } from "./IndustryWarehouse"; + +export class Industry extends BaseReactComponent { + constructor(props) { + if (props.currentCity == null) { + throw new Error(`Industry component constructed without 'city' prop`); + } + + super(props); + } + + render() { + return ( +
+
+ + +
+ +
+ +
+
+ ) + + } +} diff --git a/src/Corporation/ui/IndustryOffice.jsx b/src/Corporation/ui/IndustryOffice.jsx new file mode 100644 index 000000000..56707f8b5 --- /dev/null +++ b/src/Corporation/ui/IndustryOffice.jsx @@ -0,0 +1,557 @@ +// React Component for displaying an Industry's OfficeSpace information +// (bottom-left panel in the Industry UI) +import React from "react"; +import { BaseReactComponent } from "./BaseReactComponent"; + +import { OfficeSpace } from "../Corporation"; +import { EmployeePositions } from "../EmployeePositions"; + +import { numeralWrapper } from "../../ui/numeralFormat"; + +import { getSelectText } from "../../../utils/uiHelpers/getSelectData"; + +export class IndustryOffice extends BaseReactComponent { + constructor(props) { + super(props); + + this.state = { + employeeManualAssignMode: false, + employee: null, // Reference to employee being referenced if in Manual Mode + numEmployees: 0, + numOperations: 0, + numEngineers: 0, + numBusiness: 0, + numManagement: 0, + numResearch: 0, + numUnassigned: 0, + numTraining: 0, + } + + this.updateEmployeeCount(); // This function validates division and office refs + } + + updateEmployeeCount() { + const division = this.routing().currentDivision; + if (division == null) { + throw new Error(`Routing does not hold reference to the current Industry`); + } + const office = division.offices[this.props.currentCity]; + if (!(office instanceof OfficeSpace)) { + throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`); + } + + // Calculate how many NEW emplyoees we need to account for + const currentNumEmployees = office.employees.length; + const newEmployees = currentNumEmployees - this.state.numEmployees; + + // Record the number of employees in each position, for NEW employees only + for (let i = this.state.numEmployees; i < office.employees.length; ++i) { + switch (office.employees[i].pos) { + case EmployeePositions.Operations: + ++this.state.numOperations; + break; + case EmployeePositions.Engineer: + ++this.state.numEngineers; + break; + case EmployeePositions.Business: + ++this.state.numBusiness; + break; + case EmployeePositions.Management: + ++this.state.numManagement; + break; + case EmployeePositions.RandD: + ++this.state.numResearch; + break; + case EmployeePositions.Unassigned: + ++this.state.numUnassigned; + break; + case EmployeePositions.Training: + ++this.state.numTraining; + break; + default: + console.error("Unrecognized employee position: " + office.employees[i].pos); + break; + } + } + + this.state.numEmployees = currentNumEmployees; + } + + // Renders the "Employee Management" section of the Office UI + renderEmployeeManagement() { + this.updateEmployeeCount(); + + if (this.state.employeeManualAssignMode) { + return this.renderManualEmployeeManagement(); + } else { + return this.renderAutomaticEmployeeManagement(); + } + } + + renderAutomaticEmployeeManagement() { + const division = this.routing().currentDivision; // Validated in constructor + const office = division.offices[this.props.currentCity]; // Validated in constructor + const vechain = (this.corp().unlockUpgrades[4] === 1); // Has Vechain upgrade + + const switchModeOnClick = () => { + this.state.employeeManualAssignMode = true; + this.corp().rerender(); + } + + // Calculate average morale, happiness, and energy. Also salary + // TODO is this efficient? + let totalMorale = 0, totalHappiness = 0, totalEnergy = 0, totalSalary = 0; + for (let i = 0; i < office.employees.length; ++i) { + totalMorale += office.employees[i].mor; + totalHappiness += office.employees[i].hap; + totalEnergy += office.employees[i].ene; + totalSalary += office.employees[i].sal; + } + + let avgMorale = 0, avgHappiness = 0, avgEnergy = 0; + if (office.employees.length > 0) { + avgMorale = totalMorale / office.employees.length; + avgHappiness = totalHappiness / office.employees.length; + avgEnergy = totalEnergy / office.employees.length; + } + + // Helper functions for (re-)assigning employees to different positions + const assignEmployee = (to) => { + if (this.state.numUnassigned >= 0) { + console.warn("Cannot assign employee. No unassigned employees available"); + return; + } + + switch (to) { + case EmployeePositions.Operations: + ++this.state.numOperations; + break; + case EmployeePositions.Engineer: + ++this.state.numEngineers; + break; + case EmployeePositions.Business: + ++this.state.numBusiness; + break; + case EmployeePositions.Management: + ++this.state.numManagement; + break; + case EmployeePositions.RandD: + ++this.state.numResearch; + break; + case EmployeePositions.Unassigned: + ++this.state.numUnassigned; + break; + case EmployeePositions.Training: + ++this.state.numTraining; + break; + default: + console.error("Unrecognized employee position: " + to); + break; + } + --this.state.numUnassigned; + + office.assignEmployeeToJob(to); + this.corp().rerender(); + } + + const unassignEmployee = (from) => { + function logWarning(pos) { + console.warn(`Cannot unassign from ${pos} because there is nobody assigned to that position`); + } + + switch (from) { + case EmployeePositions.Operations: + if (this.state.numOperations <= 0) { return logWarning(EmployeePositions.Operations); } + --this.state.numOperations; + break; + case EmployeePositions.Engineer: + if (this.state.numEngineers <= 0) { return logWarning(EmployeePositions.Operations); } + --this.state.numEngineers; + break; + case EmployeePositions.Business: + if (this.state.numBusiness <= 0) { return logWarning(EmployeePositions.Operations); } + --this.state.numBusiness; + break; + case EmployeePositions.Management: + if (this.state.numManagement <= 0) { return logWarning(EmployeePositions.Operations); } + --this.state.numManagement; + break; + case EmployeePositions.RandD: + if (this.state.numResearch <= 0) { return logWarning(EmployeePositions.Operations); } + --this.state.numResearch; + break; + case EmployeePositions.Unassigned: + console.warn(`Tried to unassign from the Unassigned position`); + break; + case EmployeePositions.Training: + if (this.state.numTraining <= 0) { return logWarning(EmployeePositions.Operations); } + --this.state.numTraining; + break; + default: + console.error("Unrecognized employee position: " + from); + break; + } + ++this.state.numUnassigned; + + office.unassignEmployeeFromJob(from); + this.corp().rerender(); + } + + const positionHeaderStyle = { + fontSize: "15px", + margin: "5px 0px 5px 0px", + width: "50%", + } + const assignButtonClass = this.state.numUnassigned > 0 ? "std-button" : "a-link-button-inactive"; + + const operationAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.Operations); + this.corp().rerender(); + } + const operationUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.Operations); + this.corp().rerender(); + } + const operationUnassignButtonClass = this.state.numOperations > 0 ? "std-button" : "a-link-button-inactive"; + + const engineerAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.Engineer); + this.corp().rerender(); + } + const engineerUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.Engineer); + this.corp().rerender(); + } + const engineerUnassignButtonClass = this.state.numEngineers > 0 ? "std-button" : "a-link-button-inactive"; + + const businessAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.Business); + this.corp().rerender(); + } + const businessUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.Business); + this.corp().rerender(); + } + const businessUnassignButtonClass = this.state.numBusiness > 0 ? "std-button" : "a-link-button-inactive"; + + const managementAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.Management); + this.corp().rerender(); + } + const managementUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.Management); + this.corp().rerender(); + } + const managementUnassignButtonClass = this.state.numManagement > 0 ? "std-button" : "a-link-button-inactive"; + + const rndAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.RandD); + this.corp().rerender(); + } + const rndUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.RandD); + this.corp().rerender(); + } + const rndUnassignButtonClass = this.state.numResearch > 0 ? "std-button" : "a-link-button-inactive"; + + const trainingAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.Training); + this.corp().rerender(); + } + const trainingUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.Training); + this.corp().rerender(); + } + const trainingUnassignButtonClass = this.state.numTraining > 0 ? "std-button" : "a-link-button-inactive"; + + return ( +
+ + +

Unassigned Employees: {this.state.numUnassigned}

+
+ +

Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}

+

Avg Happiness Morale: {numeralWrapper.format(avgHappiness, "0.000")}

+

Avg Energy Morale: {numeralWrapper.format(avgEnergy, "0.000")}

+

Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}

+ { + vechain && +
+

+ Material Production: {numeralWrapper.format(division.getOfficeProductivity(office), "0.000")} + + The base amount of material this office can produce. Does not include + production multipliers from upgrades and materials. This value is based off + the productivity of your Operations, Engineering, and Management employees + +


+

+ Product Production: {numeralWrapper.format(division.getOfficeProductivity(office, {forProduct:true}), "0.000")} + + The base amount of any given Product this office can produce. Does not include + production multipliers from upgrades and materials. This value is based off + the productivity of your Operations, Engineering, and Management employees + +


+

+ Business Multiplier: x" ${numeralWrapper.format(division.getBusinessFactor(office), "0.000")} + + The effect this office's 'Business' employees has on boosting sales + +


+
+ } + +

+ {EmployeePositions.Operations} + + Manages supply chain operations. Improves production. + +

+ + +
+ +

+ {EmployeePositions.Engineer} + + Develops and maintains products and production systems. Improves production. + +

+ + +
+ +

+ {EmployeePositions.Business} + + Handles sales and finances. Improves sales. + +

+ + +
+ +

+ {EmployeePositions.Management} + + Leads and oversees employees and office operations. Improves production. + +

+ + +
+ +

+ {EmployeePositions.RandD} + + Research new innovative ways to improve the company. Generates Scientific Research + +

+ + +
+ +

+ {EmployeePositions.Training} + + Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations. + +

+ + +
+ ) + } + + renderManualEmployeeManagement() { + const corp = this.corp(); + const division = this.routing().currentDivision; // Validated in constructor + const office = division.offices[this.props.currentCity]; // Validated in constructor + + const switchModeOnClick = () => { + this.state.employeeManualAssignMode = false; + this.corp().rerender(); + } + + const employeeInfoDivStyle = { + color: "white", + margin: "4px", + padding: "4px", + } + + // Employee Selector + const employees = []; + for (let i = 0; i < office.employees.length; ++i) { + employees.push() + } + + const employeeSelectorOnChange = (e) => { + const name = getSelectText(e.target); + for (let i = 0; i < office.employees.length; ++i) { + if (name === office.employees[i].name) { + this.state.employee = office.employees[i]; + } + } + } + + const employeeSelectorStyle = { + backgroundColor: "black", + color: "white", + margin: "4px", + padding: "4px", + } + + // Employee Positions Selector + const employeePositions = []; + const positionNames = Object.values(EmployeePositions); + for (let i = 0; i < positionNames.length; ++i) { + employeePositions.push(); + } + + const employeePositionSelectorOnChange = (e) => { + const pos = getSelectText(e.target); + this.state.employee.pos = pos; + } + + // Numeraljs formatter + const nf = "0.000"; + + // Employee stats (after applying multipliers) + const emp = this.state.employee; + const effCre = emp ? emp.cre * corp.getEmployeeCreMultiplier() * division.getEmployeeCreMultiplier() : 0; + const effCha = emp ? emp.cha * corp.getEmployeeChaMultiplier() * division.getEmployeeChaMultiplier() : 0; + const effInt = emp ? emp.int * corp.getEmployeeIntMultiplier() * division.getEmployeeIntMultiplier() : 0; + const effEff = emp ? emp.eff * corp.getEmployeeEffMultiplier() * division.getEmployeeEffMultiplier() : 0; + + return ( +
+ + +
+ { + this.state.employee != null && +

+ Morale: {numeralWrapper.format(this.state.employee.mor, nf)} +
+ Happiness: {numeralWrapper.format(this.state.employee.hap, nf)} +
+ Energy: {numeralWrapper.format(this.state.employee.ene, nf)} +
+ Age: {numeralWrapper.format(this.state.employee.age, nf)} +
+ Intelligence: {numeralWrapper.format(effInt, nf)} +
+ Charisma: {numeralWrapper.format(effCha, nf)} +
+ Experience: {numeralWrapper.format(this.state.employee.exp, nf)} +
+ Creativity: {numeralWrapper.format(effCre, nf)} +
+ Efficiency: {numeralWrapper.format(effEff, nf)} +
+ Salary: {numeralWrapper.formatMoney(this.state.employee.sal)} +

+ } + { + this.state.employee != null && + + } +
+ + +
+ ) + } + + render() { + const corp = this.corp(); + const division = this.routing().currentDivision; // Validated in constructor + const office = division.offices[this.props.currentCity]; // Validated in constructor + + const buttonStyle = { + fontSize: "13px", + } + + // Hire Employee button + let hireEmployeeButtonClass = "std-button tooltip"; + if (office.employees.length === 0) { + hireEmployeeButtonClass += " flashing-button"; + } + const hireEmployeeButtonOnClick = () => { + office.findEmployees({ corporation: corp, industry: division }); + } + + // Autohire employee button + const autohireEmployeeButtonOnClick = () => { + office.hireRandomEmployee({ corporation: corp, industry: division }); + this.corp().rerender(); + } + + // Upgrade Office Size Button + const upgradeOfficeSizeOnClick = this.eventHandler().createUpgradeOfficeSizePopup.bind(this.eventHandler(), office); + + // Throw Office Party + const throwOfficePartyOnClick = this.eventHandler().createThrowOfficePartyPopup.bind(this.eventHandler(), office); + + return ( +
+

Office Space

+

Size: {office.employees.length} / {office.size} employees

+ + +
+ + { + !division.hasResearch("AutoPartyManager") && + + } +
+ + {this.renderEmployeeManagement()} +
+ ) + } +} diff --git a/src/Corporation/ui/IndustryOverview.jsx b/src/Corporation/ui/IndustryOverview.jsx new file mode 100644 index 000000000..086c5df2a --- /dev/null +++ b/src/Corporation/ui/IndustryOverview.jsx @@ -0,0 +1,261 @@ +// React Component for displaying an Industry's overview information +// (top-left panel in the Industry UI) +import React from "react"; +import { BaseReactComponent } from "./BaseReactComponent"; + +import { OfficeSpace } from "../Corporation"; +import { Industries } from "../IndustryData"; +import { IndustryUpgrades } from "../IndustryUpgrades"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; + +export class IndustryOverview extends BaseReactComponent { + renderMakeProductButton() { + const corp = this.corp(); + const division = this.routing().currentDivision; // Validated inside render() + + var createProductButtonText, createProductPopupText; + switch(division.type) { + case Industries.Food: + createProductButtonText = "Build Restaurant"; + createProductPopupText = "Build and manage a new restaurant!" + break; + case Industries.Tobacco: + createProductButtonText = "Create Product"; + createProductPopupText = "Create a new tobacco product!"; + break; + case Industries.Pharmaceutical: + createProductButtonText = "Create Drug"; + createProductPopupText = "Design and develop a new pharmaceutical drug!"; + break; + case Industries.Computer: + case "Computer": + createProductButtonText = "Create Product"; + createProductPopupText = "Design and manufacture a new computer hardware product!"; + break; + case Industries.Robotics: + createProductButtonText = "Design Robot"; + createProductPopupText = "Design and create a new robot or robotic system!"; + break; + case Industries.Software: + createProductButtonText = "Develop Software"; + createProductPopupText = "Develop a new piece of software!"; + break; + case Industries.Healthcare: + createProductButtonText = "Build Hospital"; + createProductPopupText = "Build and manage a new hospital!"; + break; + case Industries.RealEstate: + createProductButtonText = "Develop Property"; + createProductPopupText = "Develop a new piece of real estate property!"; + break; + default: + createProductButtonText = "Create Product"; + createProductPopupText = "Create a new product!"; + return ""; + } + createProductPopupText += "

To begin developing a product, " + + "first choose the city in which it will be designed. The stats of your employees " + + "in the selected city affect the properties of the finished product, such as its " + + "quality, performance, and durability.

" + + "You can also choose to invest money in the design and marketing of " + + "the product. Investing money in its design will result in a superior product. " + + "Investing money in marketing the product will help the product's sales."; + + const hasMaxProducts = division.hasMaximumNumberProducts(); + + const className = hasMaxProducts ? "a-link-button-inactive tooltip" : "std-button"; + const onClick = this.eventHandler().createMakeProductPopup.bind(this.eventHandler(), createProductPopupText, division); + const buttonStyle = { + margin: "6px", + display: "inline-block", + } + + return ( + + ) + } + + renderText() { + const corp = this.corp(); + const division = this.routing().currentDivision; // Validated inside render() + + const vechain = (corp.unlockUpgrades[4] === 1); + const profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber(); + + const genInfo = `Industry: ${division.type} (Corp Funds: ${numeralWrapper.formatMoney(corp.funds.toNumber())})`; + const awareness = `Awareness: ${numeralWrapper.format(division.awareness, "0.000")}`; + const popularity = `Popularity: ${numeralWrapper.format(division.popularity, "0.000")}`; + + let advertisingInfo = false; + let advertisingTooltip; + const advertisingFactors = division.getAdvertisingFactors(); + const awarenessFac = advertisingFactors[1]; + const popularityFac = advertisingFactors[2]; + const ratioFac = advertisingFactors[3]; + const totalAdvertisingFac = advertisingFactors[0]; + if (vechain) { advertisingInfo = true; } + + const revenue = `Revenue: ${numeralWrapper.formatMoney(division.lastCycleRevenue.toNumber())} / s`; + const expenses = `Expenses: ${numeralWrapper.formatMoney(division.lastCycleExpenses.toNumber())} /s`; + const profitStr = `Profit: ${numeralWrapper.formatMoney(profit)} / s`; + + const productionMultHelpTipOnClick = () => { + dialogBoxCreate("Owning Hardware, Robots, AI Cores, and Real Estate " + + "can boost your Industry's production. The effect these " + + "materials have on your production varies between Industries. " + + "For example, Real Estate may be very effective for some Industries, " + + "but ineffective for others.

" + + "This division's production multiplier is calculated by summing " + + "the individual production multiplier of each of its office locations. " + + "This production multiplier is applied to each office. Therefore, it is " + + "beneficial to expand into new cities as this can greatly increase the " + + "production multiplier of your entire Division."); + } + + return ( +
+ {genInfo} +

+ {awareness}
+ {popularity}
+ { + (advertisingInfo !== false) && +

Advertising Multiplier: {numeralWrapper.format(totalAdvertisingFac, "0.000")} + + Total multiplier for this industrys sales due to its awareness and popularity +
+ Awareness Bonus: x{formatNumber(Math.pow(awarenessFac, 0.85), 3)} +
+ Popularity Bonus: x{formatNumber(Math.pow(popularityFac, 0.85), 3)} +
+ Ratio Multiplier: x{formatNumber(Math.pow(ratioFac, 0.85), 3)} +
+

+ } + {advertisingInfo} +

+ {revenue}
+ {expenses}
+ {profitStr} +

+

+ Production Multiplier: {numeralWrapper.format(division.prodMult, "0.00")} + + Production gain from owning production-boosting materials + such as hardware, Robots, AI Cores, and Real Estate + +

+
?
+

+

+ Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000")} + + Scientific Research increases the quality of the materials and + products that you produce. + +

+ +
?
+
+ ) + } + + renderUpgrades() { + const corp = this.corp(); + const division = this.routing().currentDivision; // Validated inside render() + const office = division.offices[this.props.currentCity]; + if (!(office instanceof OfficeSpace)) { + throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`); + } + + const upgrades = []; + for (const index in IndustryUpgrades) { + const upgrade = IndustryUpgrades[index]; + + // AutoBrew research disables the Coffee upgrade + if (division.hasResearch("AutoBrew") && upgrade[4] === "Coffee") { continue; } + + const i = upgrade[0]; + const baseCost = upgrade[1]; + const priceMult = upgrade[2]; + let cost = 0; + switch (i) { + case 0: //Coffee, cost is static per employee + cost = office.employees.length * baseCost; + break; + default: + cost = baseCost * Math.pow(priceMult, division.upgrades[i]); + break; + } + + const onClick = () => { + if (corp.funds.lt(cost)) { + dialogBoxCreate("Insufficient funds"); + } else { + corp.funds = corp.funds.minus(cost); + division.upgrade(upgrade, { + corporation: corp, + office: office, + }); + // corp.displayDivisionContent(division, city); + corp.rerender(); + } + } + + upgrades.push(this.renderUpgrade({ + onClick: onClick, + text: `${upgrade[4]} - ${numeralWrapper.formatMoney(cost)}`, + tooltip: upgrade[5], + })); + } + + return upgrades; + } + + renderUpgrade(props) { + return ( +
+ {props.text} + { + props.tooltip != null && + {props.tooltip} + } +
+ ) + } + + render() { + const division = this.routing().currentDivision; + if (division == null) { + throw new Error(`Routing does not hold reference to the current Industry`); + } + + const makeProductButton = this.renderMakeProductButton(); + + return ( +
+ {this.renderText()} +
+ + Purchases & Upgrades
+ {this.renderUpgrades()}
+ + { + division.makesProducts && + {makeProductButton} + } +
+ ) + } +} diff --git a/src/Corporation/ui/IndustryWarehouse.jsx b/src/Corporation/ui/IndustryWarehouse.jsx new file mode 100644 index 000000000..3c643ed6e --- /dev/null +++ b/src/Corporation/ui/IndustryWarehouse.jsx @@ -0,0 +1,486 @@ +// React Component for displaying an Industry's warehouse information +// (right-side panel in the Industry UI) +import React from "react"; +import { BaseReactComponent } from "./BaseReactComponent"; + +import { Material } from "../Material"; +import { Product } from "../Product"; + +import { Warehouse, + WarehouseInitialCost, + WarehouseUpgradeBaseCost } from "../Corporation"; + +import { numeralWrapper } from "../../ui/numeralFormat"; + +import { isString } from "../../../utils/helpers/isString"; + +// Creates the UI for a single Product type +function ProductComponent(props) { + const corp = props.corp; + const division = props.division; + const warehouse = props.warehouse; + const product = props.product; + const eventHandler = props.eventHandler; + + const nf = "0.000"; // Numeraljs formatter + + const hasUpgradeDashboard = division.hasResearch("uPgrade: Dashboard"); + + // Total product gain = production - sale + const totalGain = totalGain = product.data[city][1] - product.data[city][2]; + + // Sell button + const sellButtonText = product.sllman[city][1] === -1 + ? "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/MAX)" + : "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/" + numeralWrapper.format(product.sllman[city][1], nf) + ")"; + if (product.sCost) { + if (isString(product.sCost)) { + sellButtonText += (" @ " + product.sCost); + } else { + sellButtonText += (" @ " + numeralWrapper.format(product.sCost, "$0.000a")); + } + } + const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product); + + // Limit Production button + const limitProductionButtonText = "Limit Production"; + if (product.prdman[city][0]) { + limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")"; + } + const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product); + + // Discontinue Button + const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product); + + // Unfinished Product + if (!product.fin) { + if (hasUpgradeDashboard) { + return ( +
+

Designing {product.name}...

+

{numeralWrapper.format(product.prog, "0.00")}% complete

+
+ +
+
+ + +
+
+ ) + } else { + return ( +
+

Designing {product.name}...

+

{numeralWrapper.format(product.prog, "0.00")}% complete

+
+ ); + } + } + + return ( +
+

+ {product.name}: {numeralWrapper.format(product.data[city][0], nf)} ({numeralWrapper.format(totalGain, nf)}/s) + + Prod: {numeralWrapper.format(product.data[city][1], nf)}/s +
+ Sell: {numeralWrapper.format(product.data[city][2], nf)} /s +
+

+

+ Rating: {numeralWrapper.format(product.rat, nf)} + + Quality: {numeralWrapper.format(product.qlt, nf)}
+ Performance: {numeralWrapper.format(product.per, nf)}
+ Durability: {numeralWrapper.format(product.dur, nf)}
+ Reliability: {numeralWrapper.format(product.rel, nf)}
+ Aesthetics: {numeralWrapper.format(product.aes, nf)}
+ Features: {numeralWrapper.format(product.fea, nf)} + { + corp.unlockUpgrades[2] === 1 &&
+ } + { + corp.unlockUpgrades[2] === 1 && + "Demand: " + numeralWrapper.format(product.dmd, nf) + } + { + corp.unlockUpgrades[3] === 1 &&
+ } + { + corp.unlockUpgrades[3] === 1 && + "Competition: " + numeralWrapper.format(product.cmp, nf) + } + +
+

+

+ Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)} + + An estimate of the material cost it takes to create this Product. + +

+

+ Est. Market Price: {numeralWrapper.formatMoney(product.pCost + product.rat / product.mku)} + + An estimate of how much consumers are willing to pay for this product. + Setting the sale price above this may result in less sales. Setting the sale price below this may result + in more sales. + +

+ +
+
+ + +
+
+ ) +} + +// Creates the UI for a single Material type +function MaterialComponent(props) { + const corp = props.corp; + const division = props.division; + const warehouse = props.warehouse; + const mat = props.mat; + const eventHandler = props.eventHandler; + + // Numeraljs formatter + const nf = "0.000"; + + // Total gain or loss of this material (per second) + const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp; + + // Competition and demand info, if they're unlocked + let cmpAndDmdText = ""; + if (corp.unlockUpgrades[2] === 1) { + cmpAndDmdText += "
Demand: " + numeralWrapper.format(mat.dmd, nf); + } + if (corp.unlockUpgrades[3] === 1) { + cmpAndDmdText += "
Competition: " + numeralWrapper.format(mat.cmp, nf); + } + + // Flag that determines whether this industry is "new" and the current material should be + // marked with flashing-red lights + const tutorial = division.newInd && Object.keys(division.reqMats).includes(mat.name) && + mat.buy === 0 && mat.imp === 0; + + // Purchase material button + const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nf)})`; + const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button"; + const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division); + + // Export material button + const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat); + + // Sell material button + let sellButtonText; + if (mat.sllman[0]) { + sellButtonText = (mat.sllman[1] === -1 ? "Sell (" + numeralWrapper.format(mat.sll, nf) + "/MAX)" : + "Sell (" + numeralWrapper.format(mat.sll, nf) + "/" + numeralWrapper.format(mat.sllman[1], nf) + ")"); + if (mat.sCost) { + if (isString(mat.sCost)) { + var sCost = mat.sCost.replace(/MP/g, mat.bCost); + sellButtonText += " @ $" + numeralWrapper.format(eval(sCost), "0.00"); + } else { + sellButtonText += " @ $" + numeralWrapper.format(mat.sCost, "0.00"); + } + } + } else { + sellButtonText = "Sell (0.000/0.000)"; + } + const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat); + + // Market TA button + const marketTaButtonOnClick = eventHandler.createMarketTaPopup.bind(eventHandler, mat, division); + + return ( +
+
+

+ {mat.name}: {numeralWrapper.format(mat.qty, nf)} ({numeralWrapper.format(totalGain, nf)}/s) + + Buy: {numeralWrapper.format(mat.buy, nf)}
+ Prod: {numeralWrapper.format(mat.prd, nf)}
+ Sell: {numeralWrapper.format(mat.sll, nf)}
+ Export: {numeralWrapper.format(mat.totalExp, nf)}
+ Import: {numeralWrapper.format(mat.imp, nf)} + { + corp.unlockUpgrades[2] === 1 &&
+ } + { + corp.unlockUpgrades[2] === 1 && + "Demand: " + numeralWrapper.format(mat.dmd, nf) + } + { + corp.unlockUpgrades[3] === 1 &&
+ } + { + corp.unlockUpgrades[3] === 1 && + "Competition: " + numeralWrapper.format(mat.cmp, nf) + } +
+


+

+ MP: {numeralWrapper.formatMoney(mat.bCost)} + + Market Price: The price you would pay if you were to buy this material on the market + +


+

+ Quality: {numeralWrapper.format(mat.qlt, "0.00")} + + The quality of your material. Higher quality will lead to more sales + +

+
+ +
+ + + { + corp.unlockUpgrades[0] === 1 && + + } +
+ + + + { + division.hasResearch("Market-TA.I") && + + } + +
+
+ ) +} + +export class IndustryWarehouse extends BaseReactComponent { + renderWarehouseUI() { + const corp = this.corp(); + const division = this.routing().currentDivision; // Validated in render() + const warehouse = division.warehouses[this.props.currentCity]; // Validated in render() + + // General Storage information at the top + const sizeUsageStyle = { + color: warehouse.sizeUsed >= warehouse.size ? "red" : "white", + margin: "5px", + } + + // Upgrade Warehouse size button + const sizeUpgradeCost = WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1); + const canAffordUpgrade = (corp.funds.gt(sizeUpgradeCost)); + const upgradeWarehouseClass = canAffordUpgrade ? "std-button" : "a-link-button-inactive"; + const upgradeWarehouseOnClick = () => { + ++warehouse.level; + warehouse.updateSize(corp, division); + corp.funds = corp.funds.minus(sizeUpgradeCost); + warehouse.createUI(parentRefs); + return; + } + + // Industry material Requirements + let generalReqsText = "This Industry uses [" + Object.keys(division.reqMats).join(", ") + + "] in order to "; + if (division.prodMats.length > 0) { + generalReqsText += "produce [" + division.prodMats.join(", ") + "] "; + if (division.makesProducts) { + generalReqsText += " and " + division.getProductDescriptionText(); + } + } else if (division.makesProducts) { + generalReqsText += (division.getProductDescriptionText() + "."); + } + + const ratioLines = []; + for (const matName in division.reqMats) { + if (division.reqMats.hasOwnProperty(matName)) { + const text = [" *", division.reqMats[matName], matName].join(" "); + ratioLines.push(( +
+

{text}

+
+ )); + } + } + + let createdItemsText = "in order to create "; + if (division.prodMats.length > 0) { + createdItemsText += "one of each produced Material (" + division.prodMats.join(", ") + ") "; + if (division.makesProducts) { + createdItemsText += "or to create one of its Products"; + } + } else if (division.makesProducts) { + createdItemsText += "one of its Products"; + } + + // Current State: + let stateText; + switch(division.state) { + case "START": + stateText = "Current state: Preparing..."; + break; + case "PURCHASE": + stateText = "Current state: Purchasing materials..."; + break; + case "PRODUCTION": + stateText = "Current state: Producing materials and/or products..."; + break; + case "SALE": + stateText = "Current state: Selling materials and/or products..."; + break; + case "EXPORT": + stateText = "Current state: Exporting materials and/or products..."; + break; + default: + console.error(`Invalid state: ${division.state}`); + break; + } + + // Smart Supply Checkbox + const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox"; + const smartSupplyOnChange = (e) => { + warehouse.smartSupplyEnabled = e.target.value; + } + + // Materials that affect Production multiplier + const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"]; + + // Returns a boolean indicating whether the given material is relevant for the + // current industry. + function isRelevantMaterial(matName) { + if (Object.keys(division.reqMats).includes(matName)) { return true; } + if (division.prodMats.includes(matName)) { return true; } + if (prodMultiplierMats.includes(matName)) { return true; } + + return false; + } + + // Create React components for materials + const mats = []; + for (const matName in warehouse.materials) { + if (warehouse.materials[matName] instanceof Material) { + // Only create UI for materials that are relevant for the industry + if (isRelevantMaterial(matName)) { + mats.push(MaterialComponent({ + corp: corp, + division: division, + eventHandler: this.eventHandler(), + key: matName, + mat: warehouse.materials[matName], + warehouse: warehouse, + })); + } + } + } + + // Create React components for products + const products = []; + if (division.makesProducts && Object.keys(division.products).length > 0) { + for (const productName in division.products) { + if (division.products[productName] instanceof Product) { + products.push({ + corp: corp, + division: division, + eventHandler: this.eventHandler(), + key: productName, + product: division.products[productName], + warehouse: warehouse, + }) + } + } + } + + return ( +
+

+ Storage: {numeralWrapper.format(warehouse.sizeUsed, "0.000")} / {numeralWrapper.format(warehouse.size, "0.000")} + + {warehouse.breakdown} + +

+ + + +

{generalReqsText}. The exact requirements for production are:


+ {ratioLines}
+

{createdItemsText}

+

+ To get started with production, purchase your required materials + or import them from another of your company's divisions. +


+ +

{stateText}

+ + { + corp.unlockUpgrades[1] && +
+ + +
+ } + + {mats} + + {products} + +
+ ) + } + + render() { + const corp = this.corp(); + const division = this.routing().currentDivision; + if (division == null) { + throw new Error(`Routing does not hold reference to the current Industry`); + } + const warehouse = division.warehouses[this.props.currentCity]; + + const newWarehouseOnClick = this.eventHandler().purchaseWarehouse.bind(this.eventHandler(), division, this.props.currentCity); + + if (warehouse instanceof Warehouse) { + return this.renderWarehouseUI(); + } else { + return ( + + ) + } + } +} diff --git a/src/Corporation/ui/LevelableUpgrade.jsx b/src/Corporation/ui/LevelableUpgrade.jsx new file mode 100644 index 000000000..1c79c0d37 --- /dev/null +++ b/src/Corporation/ui/LevelableUpgrade.jsx @@ -0,0 +1,35 @@ +// React components for the levelable upgrade buttons on the overview panel +import React from "react"; +import { BaseReactComponent } from "./BaseReactComponent"; + +import { numeralWrapper } from "../../ui/numeralFormat"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; + +export class LevelableUpgrade extends BaseReactComponent { + render() { + const data = this.props.upgradeData; + const level = this.props.upgradeLevel; + + const baseCost = data[1]; + const priceMult = data[2]; + const cost = baseCost * Math.pow(priceMult, level); + + const text = `${data[4]} - ${numeralWrapper.formatMoney(cost)}` + const tooltip = data[5]; + const onClick = () => { + if (this.corp().funds.lt(cost)) { + dialogBoxCreate("Insufficient funds"); + } else { + this.corp().upgrade(data); + //this.corp().displayCorporationOverviewContent(); + } + } + + return ( +
+ {text} + {tooltip} +
+ ) + } +} diff --git a/src/Corporation/ui/MainPanel.jsx b/src/Corporation/ui/MainPanel.jsx new file mode 100644 index 000000000..d737c8baa --- /dev/null +++ b/src/Corporation/ui/MainPanel.jsx @@ -0,0 +1,91 @@ +// React Component for the element that contains the actual info/data +// for the Corporation UI. This panel lies below the header tabs and will +// be filled with whatever is needed based on the routing/navigation +import React from "react"; +import { BaseReactComponent } from "./BaseReactComponent"; + +import { CityTabs } from "./CityTabs"; +import { Industry } from "./Industry"; +import { Overview } from "./Overview"; +import { overviewPage } from "./Routing"; + +import { Cities } from "../../Locations/Cities"; + +export class MainPanel extends BaseReactComponent { + constructor(props) { + super(props); + + this.state = { + division: "", + city: Cities.Sector12, + } + } + + // Determines what UI content to render based on routing + renderContent() { + if (this.routing().isOnOverviewPage()) { + // Corporation overview Content + return this.renderOverviewPage(); + } else { + // Division content + + // First, check if we're at a new division. If so, we need to reset the city to Sector-12 + // Otherwise, just switch the 'city' state + const currentDivision = this.routing().current(); + if (currentDivision !== this.state.division) { + this.state.division = currentDivision; + this.state.city = Cities.Sector12; + } + + return this.renderDivisionPage(); + } + } + + renderOverviewPage() { + return ( +
+ +
+ ) + } + + renderDivisionPage() { + // Note: Division is the same thing as Industry...I wasn't consistent with naming + const division = this.routing().currentDivision; + if (division == null) { + throw new Error(`Routing does not hold reference to the current Industry`); + } + + // City tabs + const onClicks = {}; + for (const cityName in division.offices) { + onClicks[cityName] = () => { + this.state.city = cityName; + this.corp().rerender(); + } + } + const cityTabs = ( + + ) + + // Rest of Industry UI + const industry = ( + + ) + + return ( +
+ {cityTabs} + {industry} +
+ ) + } + + render() { + return this.renderContent(); + } +} diff --git a/src/Corporation/ui/Overview.jsx b/src/Corporation/ui/Overview.jsx new file mode 100644 index 000000000..6ae424863 --- /dev/null +++ b/src/Corporation/ui/Overview.jsx @@ -0,0 +1,323 @@ +// React Component for displaying Corporation Overview info +import React from "react"; +import { BaseReactComponent } from "./BaseReactComponent"; +import { LevelableUpgrade } from "./LevelableUpgrade"; +import { UnlockUpgrade } from "./UnlockUpgrade"; + +import { BribeThreshold } from "../Corporation"; +import { CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades"; +import { CorporationUpgrades } from "../data/CorporationUpgrades"; + +import { CONSTANTS } from "../../Constants"; +import { numeralWrapper } from "../../ui/numeralFormat"; + +export class Overview extends BaseReactComponent { + // Generic Function for Creating a button + createButton(props) { + let className = props.class ? props.class : "std-button"; + const displayStyle = props.display ? props.display : "block"; + const hasTooltip = (props.tooltip != null); + if (hasTooltip) { + className += " tooltip"; + } + + return ( + + {props.text} + { + hasTooltip && + + {props.tooltip} + + } + + ) + + } + + // Returns a string with general information about Corporation + getOverviewText() { + // Formatted text for profit + var profit = this.corp().revenue.minus(this.corp().expenses).toNumber(), + profitStr = profit >= 0 ? numeralWrapper.formatMoney(profit) : "-" + numeralWrapper.format(-1 * profit, "$0.000a"); + + // Formatted text for dividend information, if applicable + let dividendStr = ""; + if (this.corp().dividendPercentage > 0 && profit > 0) { + const totalDividends = (this.corp().dividendPercentage / 100) * profit; + const retainedEarnings = profit - totalDividends; + const dividendsPerShare = totalDividends / this.corp().totalShares; + const playerEarnings = this.corp().numShares * dividendsPerShare; + + dividendStr = `Retained Profits (after dividends): ${numeralWrapper.format(retainedEarnings, "$0.000a")} / s

` + + `Dividend Percentage: ${numeralWrapper.format(this.corp().dividendPercentage / 100, "0%")}
` + + `Dividends per share: ${numeralWrapper.format(dividendsPerShare, "$0.000a")} / s
` + + `Your earnings as a shareholder (Pre-Tax): ${numeralWrapper.format(playerEarnings, "$0.000a")} / s
` + + `Dividend Tax Rate: ${this.corp().dividendTaxPercentage}%
` + + `Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (this.corp().dividendTaxPercentage / 100)), "$0.000a")} / s
`; + } + + let txt = "Total Funds: " + numeralWrapper.format(this.corp().funds.toNumber(), '$0.000a') + "
" + + "Total Revenue: " + numeralWrapper.format(this.corp().revenue.toNumber(), "$0.000a") + " / s
" + + "Total Expenses: " + numeralWrapper.format(this.corp().expenses.toNumber(), "$0.000a") + "/ s
" + + "Total Profits: " + profitStr + " / s
" + + dividendStr + + "Publicly Traded: " + (this.corp().public ? "Yes" : "No") + "
" + + "Owned Stock Shares: " + numeralWrapper.format(this.corp().numShares, '0.000a') + "
" + + "Stock Price: " + (this.corp().public ? "$" + numeralWrapper.formatMoney(this.corp().sharePrice) : "N/A") + "
" + + "

Total Stock Shares: " + numeralWrapper.format(this.corp().totalShares, "0.000a") + + "" + + `Outstanding Shares: ${numeralWrapper.format(this.corp().issuedShares, "0.000a")}
` + + `Private Shares: ${numeralWrapper.format(this.corp().totalShares - this.corp().issuedShares - this.corp().numShares, "0.000a")}` + + "



"; + + const storedTime = this.corp().storedCycles * CONSTANTS.MilliPerCycle / 1000; + if (storedTime > 15) { + txt += `Bonus Time: ${storedTime} seconds

`; + } + + let prodMult = this.corp().getProductionMultiplier(), + storageMult = this.corp().getStorageMultiplier(), + advMult = this.corp().getAdvertisingMultiplier(), + empCreMult = this.corp().getEmployeeCreMultiplier(), + empChaMult = this.corp().getEmployeeChaMultiplier(), + empIntMult = this.corp().getEmployeeIntMultiplier(), + empEffMult = this.corp().getEmployeeEffMultiplier(), + salesMult = this.corp().getSalesMultiplier(), + sciResMult = this.corp().getScientificResearchMultiplier(); + if (prodMult > 1) {txt += "Production Multiplier: " + numeralWrapper.format(prodMult, "0.000") + "
";} + if (storageMult > 1) {txt += "Storage Multiplier: " + numeralWrapper.format(storageMult, "0.000") + "
";} + if (advMult > 1) {txt += "Advertising Multiplier: " + numeralWrapper.format(advMult, "0.000") + "
";} + if (empCreMult > 1) {txt += "Empl. Creativity Multiplier: " + numeralWrapper.format(empCreMult, "0.000") + "
";} + if (empChaMult > 1) {txt += "Empl. Charisma Multiplier: " + numeralWrapper.format(empChaMult, "0.000") + "
";} + if (empIntMult > 1) {txt += "Empl. Intelligence Multiplier: " + numeralWrapper.format(empIntMult, "0.000") + "
";} + if (empEffMult > 1) {txt += "Empl. Efficiency Multiplier: " + numeralWrapper.format(empEffMult, "0.000") + "
";} + if (salesMult > 1) {txt += "Sales Multiplier: " + numeralWrapper.format(salesMult, "0.000") + "
";} + if (sciResMult > 1) {txt += "Scientific Research Multiplier: " + numeralWrapper.format(sciResMult, "0.000") + "
";} + + return txt; + } + + // Render the buttons that lie below the overview text. + // These are mainly for things such as managing finances/stock + renderButtons() { + // Create a "Getting Started Guide" button that lets player view the + // handbook and adds it to the players home computer + const getStarterGuideOnClick = this.corp().getStarterGuide.bind(this.corp()); + const getStarterGuideBtn = this.createButton({ + class: "a-link-button", + display: "inline-block", + onClick: getStarterGuideOnClick, + text: "Getting Started Guide", + tooltip: "Get a copy of and read 'The Complete Handbook for Creating a Successful Corporation.' " + + "This is a .lit file that guides you through the beginning of setting up a Corporation and " + + "provides some tips/pointers for helping you get started with managing it.", + }); + + // Create a "Bribe Factions" button if your Corporation is powerful enough. + // This occurs regardless of whether you're public or private + const canBribe = (this.corp().determineValuation() >= BribeThreshold); + const bribeFactionsOnClick = this.eventHandler().createBribeFactionsPopup.bind(this.eventHandler()); + const bribeFactionsClass = (canBribe ? "a-link-button" : "a-link-button-inactive"); + const bribeFactionsBtn = this.createButton({ + class: bribeFactionsClass, + display: "inline-block", + onClick: bribeFactionsOnClick, + text: "Bribe Factions", + tooltip: (canBribe + ? "Use your Corporations power and influence to bribe Faction leaders in exchange for reputation" + : "Your Corporation is not powerful enough to bribe Faction leaders"), + + }); + + const generalBtns = { + bribeFactions: bribeFactionsBtn, + getStarterGuide: getStarterGuideBtn, + }; + + if (this.corp().public) { + return this.renderPublicButtons(generalBtns); + } else { + return this.renderPrivateButtons(generalBtns); + } + } + + + // Render the buttons for when your Corporation is still private + renderPrivateButtons(generalBtns) { + const fundingAvailable = (this.corp().fundingRound < 4); + const findInvestorsClassName = fundingAvailable ? "std-button" : "a-link-button-inactive"; + const findInvestorsTooltip = fundingAvailable ? "Search for private investors who will give you startup funding in exchangefor equity (stock shares) in your company" : null; + + const findInvestorsOnClick = this.corp().getInvestment.bind(this.corp()); + const goPublicOnClick = this.corp().goPublic.bind(this.corp()); + + const findInvestorsBtn = this.createButton({ + class: findInvestorsClassName, + onClick: findInvestorsOnClick, + style: "inline-block", + text: "Find Investors", + tooltip: findInvestorsTooltip + }); + const goPublicBtn = this.createButton({ + class: "std-button", + onClick: goPublicOnClick, + style: "inline-block", + text: "Go Public", + tooltip: "Become a publicly traded and owned entity. Going public " + + "involves issuing shares for an IPO. Once you are a public " + + "company, your shares will be traded on the stock market." + }); + + return ( +
+ {generalBtns.getStarterGuide} + {findInvestorsBtn} + {goPublicBtn} +
+ {generalBtns.bribeFactions} +
+ ) + + } + + // Render the buttons for when your Corporation has gone public + renderPublicButtons(generalBtns) { + const corp = this.corp(); + + const sellSharesOnClick = this.eventHandler().createSellSharesPopup.bind(this.eventHandler()); + const sellSharesOnCd = (corp.shareSaleCooldown > 0); + const sellSharesClass = sellSharesOnCd ? "a-link-button-inactive" : "std-button"; + const sellSharesTooltip = sellSharesOnCd + ? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown) + : "Sell your shares in the company. The money earned from selling your " + + "shares goes into your personal account, not the Corporation's. " + + "This is one of the only ways to profit from your business venture." + const sellSharesBtn = this.createButton({ + class: sellSharesClass, + display: "inline-block", + onClick: sellSharesOnClick, + text: "Sell Shares", + tooltip: sellSharesTooltip, + }); + + const buybackSharesOnClick = this.eventHandler().createBuybackSharesPopup.bind(this.eventHandler()); + const buybackSharesBtn = this.createButton({ + class: "std-button", + display: "inline-block", + onClick: buybackSharesOnClick, + text: "Buyback shares", + tooltip: "Buy back shares you that previously issued or sold at market price.", + }); + + const issueNewSharesOnClick = this.eventHandler().createIssueNewSharesPopup.bind(this.eventHandler()); + const issueNewSharesOnCd = (corp.issueNewSharesCooldown > 0); + const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button"; + const issueNewSharesTooltip = issueNewSharesOnCd + ? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown) + : "Issue new equity shares to raise capital."; + const issueNewSharesBtn = this.createButton({ + class: issueNewSharesClass, + display: "inline-block", + onClick: issueNewSharesOnClick, + text: "Issue New Shares", + tooltip: issueNewSharesTooltip, + }); + + const issueDividendsOnClick = this.eventHandler().createIssueDividendsPopup.bind(this.eventHandler()); + const issueDividendsBtn = this.createButton({ + class: "std-button", + display: "inline-block", + onClick: issueDividendsOnClick, + text: "Issue Dividends", + tooltip: "Manage the dividends that are paid out to shareholders (including yourself)", + }); + + return ( +
+ {generalBtns.getStarterGuide} + {sellSharesBtn} + {buybackSharesBtn} +
+ {issueNewSharesBtn} + {issueDividendsBtn} +
+ {generalBtns.bribeFactions} +
+ ) + } + + // Render the UI for Corporation upgrades + renderUpgrades() { + // Don't show upgrades + if (this.corp().divisions.length <= 0) { return; } + + // Create an array of all Unlocks + const unlockUpgrades = []; + Object.values(CorporationUnlockUpgrades).forEach((unlockData) => { + if (this.corp().unlockUpgrades[unlockData[0]] === 0) { + unlockUpgrades.push(this.renderUnlockUpgrade(unlockData)); + } + }); + + // Create an array of properties of all unlocks + const levelableUpgradeProps = []; + for (let i = 0; i < this.corp().upgrades.length; ++i) { + const upgradeData = CorporationUpgrades[i]; + const level = this.corp().upgrades[i]; + + levelableUpgradeProps.push({ + upgradeData: upgradeData, + upgradeLevel: level, + }); + } + + + return ( +
+

Unlocks

+ {unlockUpgrades} + +

Upgrades

+ { + levelableUpgradeProps.map((data) => { + return this.renderLevelableUpgrade(data); + }) + } +
+ ) + } + + renderUnlockUpgrade(data) { + return ( + + ) + } + + renderLevelableUpgrade(data) { + return ( + + ) + + } + + render() { + return ( +
+

+ {this.renderButtons()} +
+ {this.renderUpgrades()} +
+ ) + } +} diff --git a/src/Corporation/ui/Root.jsx b/src/Corporation/ui/Root.jsx new file mode 100644 index 000000000..f393de20e --- /dev/null +++ b/src/Corporation/ui/Root.jsx @@ -0,0 +1,17 @@ +// Root React Component for the Corporation UI +import React from "react"; +import { BaseReactComponent } from "./BaseReactComponent"; + +import { HeaderTabs } from "./HeaderTabs"; +import { MainPanel } from "./MainPanel"; + +export class CorporationRoot extends BaseReactComponent { + render() { + return ( +
+ + +
+ ) + } +} diff --git a/src/Corporation/ui/Routing.ts b/src/Corporation/ui/Routing.ts new file mode 100644 index 000000000..5e2c023fb --- /dev/null +++ b/src/Corporation/ui/Routing.ts @@ -0,0 +1,96 @@ +import { IMap } from "../../types"; + +export const overviewPage: string = "Overview"; + +// Interfaces for whatever's required to sanitize routing with Corporation Data +interface IOfficeSpace { + +} + +interface IDivision { + name: string; + offices: IMap +} + +interface ICorporation { + divisions: IDivision[]; +} + +/** + * Keeps track of what content is currently being displayed for the Corporation UI + */ +export class CorporationRouting { + private currentPage: string = overviewPage; + + // Stores a reference to the Corporation instance + private corp: ICorporation; + + // Stores a reference to the Division instance that the routing is currently on + // This will be null if routing is on the overview page + currentDivision: IDivision | null = null; + + constructor(corp: ICorporation) { + this.corp = corp; + } + + current(): string { + return this.currentPage; + } + + /** + * Checks that the specified page has a valid value + */ + isValidPage(page: string): boolean { + if (page === overviewPage) { return true; } + + for (const division of this.corp.divisions) { + if (division.name === page) { return true; } + } + + return false; + } + + /** + * Returns a boolean indicating whether or not the player is on the given page + */ + isOn(page: string): boolean { + if (!this.isValidPage(page)) { return false; } + + return page === this.currentPage; + } + + isOnOverviewPage(): boolean { + return this.currentPage === overviewPage; + } + + /** + * Routes to the specified page + */ + routeTo(page: string): void { + if (!this.isValidPage(page)) { return; } + + + this.currentDivision = null; + if (page !== overviewPage) { + // Iterate through Corporation data to get a reference to the current division + for (let i = 0; i < this.corp.divisions.length; ++i) { + if (this.corp.divisions[i].name === page) { + this.currentDivision = this.corp.divisions[i]; + }; + } + + // 'currentDivision' should not be null, since the routing is either on + // the overview page or a division page + if (this.currentDivision == null) { + console.warn(`Routing could not find division ${page}`); + } + } + + this.currentPage = page; + } + + routeToOverviewPage(): void { + this.currentPage = overviewPage; + this.currentDivision = null; + } +} diff --git a/src/Corporation/ui/UnlockUpgrade.jsx b/src/Corporation/ui/UnlockUpgrade.jsx new file mode 100644 index 000000000..1ead8b199 --- /dev/null +++ b/src/Corporation/ui/UnlockUpgrade.jsx @@ -0,0 +1,29 @@ +// React Components for the Unlock upgrade buttons on the overview page +import React from "react"; +import { BaseReactComponent } from "./BaseReactComponent"; + +import { numeralWrapper } from "../../ui/numeralFormat"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; + +export class UnlockUpgrade extends BaseReactComponent { + render() { + const data = this.props.upgradeData; + const text = `${data[2]} - ${numeralWrapper.formatMoney(data[1])}`; + const tooltip = data[3]; + const onClick = () => { + if (this.corp().funds.lt(data[1])) { + dialogBoxCreate("Insufficient funds"); + } else { + this.corp().unlock(data); + //this.corp().displayCorporationOverviewContent(); + } + } + + return ( +
+ {text} + {tooltip} +
+ ) + } +} diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 38e985350..5ad3c11b7 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -44,6 +44,7 @@ import { AllServers, import { Server } from "./Server/Server"; import { GetServerByHostname, getServer, + getServerOnNetwork, numCycleForGrowth, processSingleServerGrowth } from "./Server/ServerHelpers"; import { getPurchaseServerCost, diff --git a/src/Prestige.js b/src/Prestige.js index 0f12c1e97..c764c5a37 100755 --- a/src/Prestige.js +++ b/src/Prestige.js @@ -26,10 +26,10 @@ import {Player} from "./Player"; import { AllServers, AddToAllServers, + initForeignServers, prestigeAllServers } from "./Server/AllServers"; import { Server } from "./Server/Server" -import { initForeignServers, - prestigeHomeComputer } from "./Server/ServerHelpers"; +import { prestigeHomeComputer } from "./Server/ServerHelpers"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; import { SpecialServerIps, SpecialServerIpsMap, diff --git a/src/ScriptEditor/CodeMirrorNetscriptMode.js b/src/ScriptEditor/CodeMirrorNetscriptMode.js index a457891d2..ead49e12f 100644 --- a/src/ScriptEditor/CodeMirrorNetscriptMode.js +++ b/src/ScriptEditor/CodeMirrorNetscriptMode.js @@ -208,6 +208,7 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) { "getSkillNames": atom, "startAction": atom, "stopBladeburnerAction": atom, + "getCurrentAction": atom, "getActionTime": atom, "getActionEstimatedSuccessChance": atom, "getActionCountRemaining": atom, diff --git a/src/engine.js b/src/engine.js index de21680f4..2f4ae4f79 100644 --- a/src/engine.js +++ b/src/engine.js @@ -53,9 +53,9 @@ import { getCurrentEditor, loadAllRunningScripts, scriptEditorInit, updateScriptEditorContent } from "./Script/ScriptHelpers"; -import { AllServers } from "./Server/AllServers"; +import { AllServers, + initForeignServers } from "./Server/AllServers"; import { Server } from "./Server/Server"; -import { initForeignServers } from "./Server/ServerHelpers"; import {Settings} from "./Settings/Settings"; import { initSourceFiles, SourceFiles } from "./SourceFile"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; @@ -458,8 +458,8 @@ const Engine = { if (Player.corporation instanceof Corporation) { Engine.hideAllContent(); document.getElementById("character-overview-wrapper").style.visibility = "hidden"; - Player.corporation.createUI(); routing.navigateTo(Page.Corporation); + Player.corporation.createUI(); } }, @@ -943,9 +943,7 @@ const Engine = { } if (Engine.Counters.updateDisplaysMed <= 0) { - if (routing.isOn(Page.Corporation)) { - Player.corporation.updateUIContent(); - } else if (routing.isOn(Page.CharacterInfo)) { + if (routing.isOn(Page.CharacterInfo)) { Engine.updateCharacterInfo(); } Engine.Counters.updateDisplaysMed = 9; diff --git a/tsconfig.json b/tsconfig.json index 3e238cf10..831898523 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "baseUrl" : ".", + "jsx": "react", "lib" : ["es2016", "dom"], "module": "commonjs", "target": "es6", diff --git a/webpack.config.js b/webpack.config.js index 880eef2f7..5808115ad 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -84,6 +84,13 @@ module.exports = (env, argv) => { loader: 'ts-loader', exclude: /node_modules/ }, + { + test: /\.(jsx)$/, + exclude: /node_modules/, + use: { + loader: "babel-loader" + } + }, { test: /\.s?css$/, use: [ @@ -125,7 +132,8 @@ module.exports = (env, argv) => { extensions: [ ".tsx", ".ts", - ".js" + ".js", + ".jsx", ] } }; From da97c1b4b9a5e447c2f4df172ba24ca2feacf503 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 02:39:32 -0400 Subject: [PATCH 13/34] added getBlackOpRank --- .../bladeburnerapi/getBlackOpRank.rst | 10 ++++++++++ .../netscript/netscriptbladeburnerapi.rst | 1 + src/NetscriptFunctions.js | 19 +++++++++++++++++++ src/ScriptEditor/AceNetscriptMode.js | 2 +- 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 doc/source/netscript/bladeburnerapi/getBlackOpRank.rst diff --git a/doc/source/netscript/bladeburnerapi/getBlackOpRank.rst b/doc/source/netscript/bladeburnerapi/getBlackOpRank.rst new file mode 100644 index 000000000..388e88239 --- /dev/null +++ b/doc/source/netscript/bladeburnerapi/getBlackOpRank.rst @@ -0,0 +1,10 @@ +getBlackOpRank() Netscript Function +==================================== + +.. js:function:: getBlackOpRank(name) + + :param string name: name of the BlackOp. Must be an exact match. + + Returns the rank required to complete this BlackOp. + + Returns -1 if an invalid action is specified. diff --git a/doc/source/netscript/netscriptbladeburnerapi.rst b/doc/source/netscript/netscriptbladeburnerapi.rst index 4ff1db46a..8a40b12c2 100644 --- a/doc/source/netscript/netscriptbladeburnerapi.rst +++ b/doc/source/netscript/netscriptbladeburnerapi.rst @@ -46,6 +46,7 @@ In :ref:`netscriptjs`:: setActionAutolevel() setActionLevel() getRank() + getBlackOpRank() getSkillPoints() getSkillLevel() getSkillUpgradeCost() diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 38e985350..7c661f57e 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -4195,6 +4195,25 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, "getBlackOpNames() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed " + "at the Bladeburner division or because you do not have Source-File 7"); }, + getBlackOpRank : function(name="") { + if (workerScript.checkingRam) { + return updateStaticRam("getBlackOpRank", CONSTANTS.ScriptBladeburnerApiBaseRamCost / 2); + } + updateDynamicRam("getBlackOpRank", CONSTANTS.ScriptBladeburnerApiBaseRamCost / 2); + if (Player.bladeburner instanceof Bladeburner && (Player.bitNodeN === 7 || hasBladeburner2079SF)) { + const actionId = Player.bladeburner.getActionIdFromTypeAndName('blackops', name) + if (!actionId) { + return -1; + } + const actionObj = Player.bladeburner.getActionObject(actionId); + if (!actionObj) { + return -1; + } + return actionObj.reqdRank; + } + throw makeRuntimeRejectMsg(workerScript, "getBlackOpRank() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed " + + "at the Bladeburner division or because you do not have Source-File 7"); + }, getGeneralActionNames : function() { if (workerScript.checkingRam) { return updateStaticRam("getGeneralActionNames", CONSTANTS.ScriptBladeburnerApiBaseRamCost / 10); diff --git a/src/ScriptEditor/AceNetscriptMode.js b/src/ScriptEditor/AceNetscriptMode.js index eb522d0dd..f67e9e6a5 100644 --- a/src/ScriptEditor/AceNetscriptMode.js +++ b/src/ScriptEditor/AceNetscriptMode.js @@ -117,7 +117,7 @@ let NetscriptFunctions = "getGeneralActionNames|getSkillNames|startAction|stopBladeburnerAction|" + "getActionTime|getActionEstimatedSuccessChance|getActionCountRemaining|" + "getActionMaxLevel|getActionCurrentLevel|getActionAutolevel|" + - "getActionRepGain|setActionAutolevel|setActionLevel|" + + "getActionRepGain|setActionAutolevel|setActionLevel|getBlackOpRank|" + "getRank|getSkillPoints|getSkillLevel|getSkillUpgradeCost|" + "upgradeSkill|getTeamSize|getCity|getCurrentAction|" + "setTeamSize|getCityEstimatedPopulation|getCityEstimatedCommunities|" + From 8eecb1539c67b866693dfc1aeb6e331f5b4ea018 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 13 Mar 2019 20:10:28 -0700 Subject: [PATCH 14/34] Fix bug with recent Corporation UI rewrite. Improve some styling with Corporation UI and the status text messages --- css/companymanagement.scss | 4 +- css/styles.scss | 5 +- src/Corporation/Corporation.jsx | 51 ++++++------------- src/Corporation/Warehouse.ts | 2 +- src/Corporation/data/ResearchMetadata.ts | 3 +- src/Corporation/ui/CityTabs.jsx | 4 +- .../ui/CorporationUIEventHandler.js | 38 ++++++++++---- src/Corporation/ui/IndustryOffice.jsx | 47 +++++++++-------- src/Corporation/ui/IndustryOverview.jsx | 3 +- src/Corporation/ui/IndustryWarehouse.jsx | 14 +++-- src/Corporation/ui/LevelableUpgrade.jsx | 7 +-- src/Corporation/ui/MainPanel.jsx | 10 ++-- src/Corporation/ui/UnlockUpgrade.jsx | 7 +-- src/DevMenu.js | 27 ++++++++++ src/ScriptEditor/CodeMirrorNetscriptMode.js | 1 + src/ui/createStatusText.ts | 4 +- 16 files changed, 140 insertions(+), 87 deletions(-) diff --git a/css/companymanagement.scss b/css/companymanagement.scss index 5fde635a6..bd632773f 100644 --- a/css/companymanagement.scss +++ b/css/companymanagement.scss @@ -60,7 +60,6 @@ overflow-y: auto; overflow-x: auto; overflow: visible; - padding: 2px; top: 10px; width: 45%; } @@ -69,18 +68,21 @@ border: 1px solid #fff; color: var(--my-font-color); display: inline-block; + padding: 3px; width: 100%; } .cmpy-mgmt-employee-panel { border: 1px solid #fff; display: block; + padding: 3px; width: 100%; } .cmpy-mgmt-warehouse-panel { border: 1px solid #fff; display: inline-block; + padding: 3px; width: 100%; } diff --git a/css/styles.scss b/css/styles.scss index fa9870bf9..bfdaeb278 100644 --- a/css/styles.scss +++ b/css/styles.scss @@ -203,7 +203,6 @@ a:visited { .status-text { display: inline-block; - height: 15%; position: fixed; z-index: 2; -webkit-animation: status-text 3s 1; @@ -215,10 +214,12 @@ a:visited { #status-text { background-color: transparent; - font-size: $defaultFontSize * 1.25; bottom: 0; color: #fff; + display: none; + font-size: $defaultFontSize * 1.25; margin-right: 14px; + opacity: 0; padding: 4px; right: 0; top: 0; diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx index 3ec1511a4..e8d510de2 100644 --- a/src/Corporation/Corporation.jsx +++ b/src/Corporation/Corporation.jsx @@ -751,16 +751,20 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { } var mat = warehouse.materials[matName]; + // Calculate sale cost + const markupLimit = mat.getMarkupLimit(); var sCost; - if (isString(mat.sCost)) { + if (mat.marketTa1) { + sCost = mat.bCost + markupLimit; + } else if (isString(mat.sCost)) { sCost = mat.sCost.replace(/MP/g, mat.bCost); sCost = eval(sCost); } else { sCost = mat.sCost; } - //Calculate how much of the material sells (per second) - let markup = 1, markupLimit = mat.getMarkupLimit(); + // Calculate how much of the material sells (per second) + let markup = 1; if (sCost > mat.bCost) { //Penalty if difference between sCost and bCost is greater than markup limit if ((sCost - mat.bCost) > markupLimit) { @@ -1111,7 +1115,6 @@ Industry.prototype.discontinueProduct = function(product, parentRefs) { if (this.products.hasOwnProperty(productName)) { if (product === this.products[productName]) { delete this.products[productName]; - company.updateUIContent(); } } } @@ -1534,31 +1537,6 @@ Employee.prototype.createUI = function(panel, corporation, industry) { panel.appendChild(selector); } -Employee.prototype.updateUI = function(panel, corporation, industry) { - var effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), - effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), - effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), - effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier(); - if (panel == null) { - console.log("ERROR: Employee.updateUI() called with null panel"); - return; - } - var text = document.getElementById("cmpy-mgmt-employee-" + this.name + "-panel-text"); - if (text == null) { - return this.createUI(panel); - } - text.innerHTML = "Morale: " + formatNumber(this.mor, 3) + "
" + - "Happiness: " + formatNumber(this.hap, 3) + "
" + - "Energy: " + formatNumber(this.ene, 3) + "
" + - "Age: " + formatNumber(this.age, 3) + "
" + - "Intelligence: " + formatNumber(effInt, 3) + "
" + - "Charisma: " + formatNumber(effCha, 3) + "
" + - "Experience: " + formatNumber(this.exp, 3) + "
" + - "Creativity: " + formatNumber(effCre, 3) + "
" + - "Efficiency: " + formatNumber(effEff, 3) + "
" + - "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s
"; -} - Employee.prototype.toJSON = function() { return Generic_toJSON("Employee", this); } @@ -1606,6 +1584,10 @@ function OfficeSpace(params={}) { }; } +OfficeSpace.prototype.atCapacity = function() { + return (this.employees.length) >= this.size; +} + OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) { var corporation = parentRefs.corporation, industry = parentRefs.industry; @@ -1685,6 +1667,7 @@ OfficeSpace.prototype.calculateEmployeeProductivity = function(marketCycles=1, p //Takes care of UI as well OfficeSpace.prototype.findEmployees = function(parentRefs) { var company = parentRefs.corporation, division = parentRefs.industry; + if (this.atCapacity()) { return; } if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} //Generate three random employees (meh, decent, amazing) @@ -1782,7 +1765,7 @@ OfficeSpace.prototype.hireEmployee = function(employee, parentRefs) { } employee.name = name; this.employees.push(employee); - company.displayDivisionContent(division, currentCityUi); + company.rerender(); return yesNoTxtInpBoxClose(); }); noBtn.addEventListener("click", ()=>{ @@ -1793,6 +1776,7 @@ OfficeSpace.prototype.hireEmployee = function(employee, parentRefs) { OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) { var company = parentRefs.corporation, division = parentRefs.industry; + if (this.atCapacity()) { return; } if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} //Generate three random employees (meh, decent, amazing) @@ -1822,7 +1806,6 @@ OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) { } emp.name = name; this.employees.push(emp); - company.displayDivisionContent(division, currentCityUi); } //Finds the first unassigned employee and assigns its to the specified job @@ -2021,7 +2004,7 @@ Corporation.prototype.getInvestment = function() { ++this.fundingRound; this.funds = this.funds.plus(funding); this.numShares -= investShares; - this.displayCorporationOverviewContent(); + this.rerender(); return yesNoBoxClose(); }); noBtn.addEventListener("click", ()=>{ @@ -2074,7 +2057,7 @@ Corporation.prototype.goPublic = function() { this.issuedShares = numShares; this.numShares -= numShares; this.funds = this.funds.plus(numShares * initialSharePrice); - this.displayCorporationOverviewContent(); + this.rerender(); removeElementById(goPublicPopupId); dialogBoxCreate(`You took your ${this.name} public and earned ` + `${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`); @@ -2213,8 +2196,6 @@ Corporation.prototype.upgrade = function(upgrade) { } } } - - this.updateCorporationOverviewContent(); } Corporation.prototype.getProductionMultiplier = function() { diff --git a/src/Corporation/Warehouse.ts b/src/Corporation/Warehouse.ts index a6bf7ce27..6f7a8cdd3 100644 --- a/src/Corporation/Warehouse.ts +++ b/src/Corporation/Warehouse.ts @@ -26,7 +26,7 @@ export class Warehouse { breakdown: string = ""; // Warehouse's level, which affects its maximum size - level: number = 0; + level: number = 1; // City that this Warehouse is in loc: string; diff --git a/src/Corporation/data/ResearchMetadata.ts b/src/Corporation/data/ResearchMetadata.ts index ed61a0a97..57f6f7d5e 100644 --- a/src/Corporation/data/ResearchMetadata.ts +++ b/src/Corporation/data/ResearchMetadata.ts @@ -89,7 +89,8 @@ export const researchMetadata: IConstructorParams[] = [ desc: "Develop advanced AI software that uses technical analysis to " + "help you understand and exploit the market. This research " + "allows you to know what price to sell your Materials/Products " + - "at in order to avoid losing sales due to having too high of a mark-up.", + "at in order to avoid losing sales due to having too high of a mark-up. " + + "It also lets you automatically use that sale price.", }, { name: "Market-TA.II", diff --git a/src/Corporation/ui/CityTabs.jsx b/src/Corporation/ui/CityTabs.jsx index fd99e8323..0172a6802 100644 --- a/src/Corporation/ui/CityTabs.jsx +++ b/src/Corporation/ui/CityTabs.jsx @@ -32,6 +32,8 @@ export class CityTabs extends BaseReactComponent { } render() { + const division = this.routing().currentDivision; + const tabs = []; // Tabs for each city @@ -44,7 +46,7 @@ export class CityTabs extends BaseReactComponent { } // Tab to "Expand into new City" - const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler()); + const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler(), division); tabs.push(this.renderTab({ current: false, key: "Expand into new City", diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js index be7ee277d..edbd70d56 100644 --- a/src/Corporation/ui/CorporationUIEventHandler.js +++ b/src/Corporation/ui/CorporationUIEventHandler.js @@ -239,6 +239,7 @@ export class CorporationEventHandler { clickListener:()=>{ industry.discontinueProduct(product, parentRefs); removeElementById(popupId); + this.corp.rerender(); return false; } }); @@ -669,6 +670,28 @@ export class CorporationEventHandler { ". This means that if you set the sale price higher than this, " + "you will begin to experience a loss in number of sales", }); + + // Enable using Market-TA1 for automatically setting sale price + const useTa1AutoSaleId = "cmpy-mgmt-marketa1-checkbox"; + const useTa1AutoSaleDiv = createElement("div", { display: "block" }); + const useTa1AutoSaleLabel = createElement("label", { + color: "white", + for: useTa1AutoSaleId, + innerText: "Use Market-TA.I for Auto-Sale Price", + tooltip: "If this is enabled, then this Material will automatically " + + "be sold at the price identified by Market-TA.I (i.e. the price shown above)" + }) + const useTa1AutoSaleCheckbox = createElement("input", { + id: useTa1AutoSaleId, + type: "checkbox", + value: mat.marketTa1, + changeListener: (e) => { + mat.marketTa1 = e.target.value; + } + }); + useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox); + useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox); + const closeBtn = createPopupCloseButton(popupId, { class: "std-button", display: "block", @@ -713,7 +736,7 @@ export class CorporationEventHandler { createPopup(popupId, [ta1, ta2Text, ta2Input, closeBtn]); } else { // Market-TA.I only - createPopup(popupId, [ta1, closeBtn]); + createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]); } } @@ -726,13 +749,11 @@ export class CorporationEventHandler { }); const citySelector = createElement("select", { class: "dropdown", margin:"5px" }); for (const cityName in division.offices) { - if (division.offices.hasOwnProperty(cityName)) { - if (!(division.offices[cityName] instanceof OfficeSpace)) { - citySelector.add(createElement("option", { - text: cityName, - value: cityName - })); - } + if (!(division.offices[cityName] instanceof OfficeSpace)) { + citySelector.add(createElement("option", { + text: cityName, + value: cityName + })); } } @@ -740,7 +761,6 @@ export class CorporationEventHandler { class:"std-button", display:"inline-block", innerText: "Confirm", - margin:"3px", clickListener: () => { let city = citySelector.options[citySelector.selectedIndex].value; if (this.corp.funds.lt(OfficeInitialCost)) { diff --git a/src/Corporation/ui/IndustryOffice.jsx b/src/Corporation/ui/IndustryOffice.jsx index 56707f8b5..566d44588 100644 --- a/src/Corporation/ui/IndustryOffice.jsx +++ b/src/Corporation/ui/IndustryOffice.jsx @@ -117,7 +117,7 @@ export class IndustryOffice extends BaseReactComponent { // Helper functions for (re-)assigning employees to different positions const assignEmployee = (to) => { - if (this.state.numUnassigned >= 0) { + if (this.state.numUnassigned <= 0) { console.warn("Cannot assign employee. No unassigned employees available"); return; } @@ -310,7 +310,7 @@ export class IndustryOffice extends BaseReactComponent { }

- {EmployeePositions.Operations} + {EmployeePositions.Operations} ({this.state.numOperations}) Manages supply chain operations. Improves production. @@ -320,7 +320,7 @@ export class IndustryOffice extends BaseReactComponent {

- {EmployeePositions.Engineer} + {EmployeePositions.Engineer} ({this.state.numEngineers}) Develops and maintains products and production systems. Improves production. @@ -330,7 +330,7 @@ export class IndustryOffice extends BaseReactComponent {

- {EmployeePositions.Business} + {EmployeePositions.Business} ({this.state.numBusiness}) Handles sales and finances. Improves sales. @@ -340,7 +340,7 @@ export class IndustryOffice extends BaseReactComponent {

- {EmployeePositions.Management} + {EmployeePositions.Management} ({this.state.numManagement}) Leads and oversees employees and office operations. Improves production. @@ -350,7 +350,7 @@ export class IndustryOffice extends BaseReactComponent {

- {EmployeePositions.RandD} + {EmployeePositions.RandD} ({this.state.numResearch}) Research new innovative ways to improve the company. Generates Scientific Research @@ -360,7 +360,7 @@ export class IndustryOffice extends BaseReactComponent {

- {EmployeePositions.Training} + {EmployeePositions.Training} ({this.state.numTraining}) Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations. @@ -402,13 +402,6 @@ export class IndustryOffice extends BaseReactComponent { } } - const employeeSelectorStyle = { - backgroundColor: "black", - color: "white", - margin: "4px", - padding: "4px", - } - // Employee Positions Selector const employeePositions = []; const positionNames = Object.values(EmployeePositions); @@ -475,7 +468,7 @@ export class IndustryOffice extends BaseReactComponent { } - {employees} @@ -492,16 +485,30 @@ export class IndustryOffice extends BaseReactComponent { } // Hire Employee button - let hireEmployeeButtonClass = "std-button tooltip"; - if (office.employees.length === 0) { - hireEmployeeButtonClass += " flashing-button"; + let hireEmployeeButtonClass = "tooltip"; + if (office.atCapacity()) { + hireEmployeeButtonClass += " a-link-button-inactive"; + } else { + hireEmployeeButtonClass += " std-button"; + if (office.employees.length === 0) { + hireEmployeeButtonClass += " flashing-button"; + } } + + const hireEmployeeButtonOnClick = () => { office.findEmployees({ corporation: corp, industry: division }); } // Autohire employee button + let autohireEmployeeButtonClass = "tooltip"; + if (office.atCapacity()) { + autohireEmployeeButtonClass += " a-link-button-inactive"; + } else { + autohireEmployeeButtonClass += " std-button"; + } const autohireEmployeeButtonOnClick = () => { + if (office.atCapacity()) { return; } office.hireRandomEmployee({ corporation: corp, industry: division }); this.corp().rerender(); } @@ -514,7 +521,7 @@ export class IndustryOffice extends BaseReactComponent { return (
-

Office Space

+

Office Space

Size: {office.employees.length} / {office.size} employees

- -
?
) } diff --git a/src/Corporation/ui/IndustryWarehouse.jsx b/src/Corporation/ui/IndustryWarehouse.jsx index 3c643ed6e..421ff5303 100644 --- a/src/Corporation/ui/IndustryWarehouse.jsx +++ b/src/Corporation/ui/IndustryWarehouse.jsx @@ -157,6 +157,7 @@ function MaterialComponent(props) { const warehouse = props.warehouse; const mat = props.mat; const eventHandler = props.eventHandler; + const markupLimit = mat.getMarkupLimit(); // Numeraljs formatter const nf = "0.000"; @@ -192,11 +193,13 @@ function MaterialComponent(props) { sellButtonText = (mat.sllman[1] === -1 ? "Sell (" + numeralWrapper.format(mat.sll, nf) + "/MAX)" : "Sell (" + numeralWrapper.format(mat.sll, nf) + "/" + numeralWrapper.format(mat.sllman[1], nf) + ")"); if (mat.sCost) { - if (isString(mat.sCost)) { + if (mat.marketTa1) { + sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit); + } else if (isString(mat.sCost)) { var sCost = mat.sCost.replace(/MP/g, mat.bCost); - sellButtonText += " @ $" + numeralWrapper.format(eval(sCost), "0.00"); + sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost)); } else { - sellButtonText += " @ $" + numeralWrapper.format(mat.sCost, "0.00"); + sellButtonText += " @ " + numeralWrapper.formatMoney(mat.sCost); } } } else { @@ -303,7 +306,7 @@ export class IndustryWarehouse extends BaseReactComponent { ++warehouse.level; warehouse.updateSize(corp, division); corp.funds = corp.funds.minus(sizeUpgradeCost); - warehouse.createUI(parentRefs); + corp.rerender(); return; } @@ -444,12 +447,13 @@ export class IndustryWarehouse extends BaseReactComponent { { corp.unlockUpgrades[1] &&
-
diff --git a/src/Corporation/ui/LevelableUpgrade.jsx b/src/Corporation/ui/LevelableUpgrade.jsx index 1c79c0d37..937652d8f 100644 --- a/src/Corporation/ui/LevelableUpgrade.jsx +++ b/src/Corporation/ui/LevelableUpgrade.jsx @@ -17,11 +17,12 @@ export class LevelableUpgrade extends BaseReactComponent { const text = `${data[4]} - ${numeralWrapper.formatMoney(cost)}` const tooltip = data[5]; const onClick = () => { - if (this.corp().funds.lt(cost)) { + const corp = this.corp(); + if (corp.funds.lt(cost)) { dialogBoxCreate("Insufficient funds"); } else { - this.corp().upgrade(data); - //this.corp().displayCorporationOverviewContent(); + corp.upgrade(data); + corp.rerender(); } } diff --git a/src/Corporation/ui/MainPanel.jsx b/src/Corporation/ui/MainPanel.jsx index d737c8baa..f67827feb 100644 --- a/src/Corporation/ui/MainPanel.jsx +++ b/src/Corporation/ui/MainPanel.jsx @@ -9,6 +9,8 @@ import { Industry } from "./Industry"; import { Overview } from "./Overview"; import { overviewPage } from "./Routing"; +import { OfficeSpace } from "../Corporation"; + import { Cities } from "../../Locations/Cities"; export class MainPanel extends BaseReactComponent { @@ -59,9 +61,11 @@ export class MainPanel extends BaseReactComponent { // City tabs const onClicks = {}; for (const cityName in division.offices) { - onClicks[cityName] = () => { - this.state.city = cityName; - this.corp().rerender(); + if (division.offices[cityName] instanceof OfficeSpace) { + onClicks[cityName] = () => { + this.state.city = cityName; + this.corp().rerender(); + } } } const cityTabs = ( diff --git a/src/Corporation/ui/UnlockUpgrade.jsx b/src/Corporation/ui/UnlockUpgrade.jsx index 1ead8b199..e719c73c7 100644 --- a/src/Corporation/ui/UnlockUpgrade.jsx +++ b/src/Corporation/ui/UnlockUpgrade.jsx @@ -11,11 +11,12 @@ export class UnlockUpgrade extends BaseReactComponent { const text = `${data[2]} - ${numeralWrapper.formatMoney(data[1])}`; const tooltip = data[3]; const onClick = () => { - if (this.corp().funds.lt(data[1])) { + const corp = this.corp(); + if (corp.funds.lt(data[1])) { dialogBoxCreate("Insufficient funds"); } else { - this.corp().unlock(data); - //this.corp().displayCorporationOverviewContent(); + corp.unlock(data); + corp.rerender(); } } diff --git a/src/DevMenu.js b/src/DevMenu.js index 95591491c..48a4cbee1 100644 --- a/src/DevMenu.js +++ b/src/DevMenu.js @@ -494,6 +494,29 @@ export function createDevMenu() { innerText: "Add cycles to Gang mechanic", }); + // Corporation + const corpHeader = createElement("h2", { innerText: "Corporation" }); + + const corpStoredCyclesInput = createElement("input", { + class: "text-input", + margin: "5px", + placeholder: "# Cycles to Add", + type: "number", + }); + + const corpStoredCyclesButton = createElement("button", { + class: "std-button", + clickListener: () => { + try { + const cycles = parseInt(bladeburnerStoredCyclesInput.value); + Player.corporation.storeCycles(cycles); + } catch(e) { + exceptionAlert(`Failed to add cycles to Bladeburner in dev menu: ${e}`); + } + }, + innerText: "Add Cycles to Corporation mechanic", + }); + // Coding Contracts const contractsHeader = createElement("h2", {innerText: "Coding Contracts"}); @@ -686,6 +709,10 @@ export function createDevMenu() { devMenuContainer.appendChild(gangStoredCyclesInput); devMenuContainer.appendChild(gangAddStoredCycles); devMenuContainer.appendChild(createElement("br")); + devMenuContainer.appendChild(corpHeader); + devMenuContainer.appendChild(corpStoredCyclesInput); + devMenuContainer.appendChild(corpStoredCyclesButton); + devMenuContainer.appendChild(createElement("br")); devMenuContainer.appendChild(contractsHeader); devMenuContainer.appendChild(generateRandomContractBtn); devMenuContainer.appendChild(generateRandomContractOnHomeBtn); diff --git a/src/ScriptEditor/CodeMirrorNetscriptMode.js b/src/ScriptEditor/CodeMirrorNetscriptMode.js index ead49e12f..2baf88d91 100644 --- a/src/ScriptEditor/CodeMirrorNetscriptMode.js +++ b/src/ScriptEditor/CodeMirrorNetscriptMode.js @@ -219,6 +219,7 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) { "setActionAutolevel": atom, "setActionLevel": atom, "getRank": atom, + "getBlackOpRank": atom, "getSkillPoints": atom, "getSkillLevel": atom, "getSkillUpgradeCost": atom, diff --git a/src/ui/createStatusText.ts b/src/ui/createStatusText.ts index c0ae47446..161eb047e 100644 --- a/src/ui/createStatusText.ts +++ b/src/ui/createStatusText.ts @@ -17,11 +17,13 @@ export function createStatusText(text: string) { } const statusElement: HTMLElement = getElementById("status-text"); + statusElement.style.display = "block"; statusElement.classList.add("status-text"); statusElement.innerText = text; const handler: Action = () => { - statusElement.classList.remove("status-text"); statusElement.innerText = ""; + statusElement.style.display = "none"; + statusElement.classList.remove("status-text"); }; x = setTimeoutRef(handler, threeSeconds); From 8470f307ac01f0b670f41277847a61f32388a91a Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 13 Mar 2019 20:11:48 -0700 Subject: [PATCH 15/34] Fix description for Hyperbolic Regeneration Chamber action --- src/Bladeburner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bladeburner.js b/src/Bladeburner.js index 63b43c5f0..71357a0c6 100644 --- a/src/Bladeburner.js +++ b/src/Bladeburner.js @@ -3953,7 +3953,7 @@ function initBladeburner() { GeneralActions[actionName] = new Action({ name: actionName, desc: "Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. " + - "This will slowly heal your wounds and slightly increase your stamina gain.

", + "This will slowly heal your wounds and slightly increase your stamina.

", }); //Black Operations From b4057fcb26263a16827b4e96a98a52cf1e8c56ab Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Thu, 14 Mar 2019 00:56:48 -0400 Subject: [PATCH 16/34] Character overview is now through react --- src/CharacterOverview.js | 59 ------------------------------ src/Corporation/Corporation.jsx | 2 +- src/{engine.js => engine.jsx} | 7 ++-- src/index.html | 30 +-------------- src/ui/React/CharacterOverview.jsx | 54 +++++++++++++++++++++++++++ webpack.config.js | 2 +- 6 files changed, 61 insertions(+), 93 deletions(-) delete mode 100644 src/CharacterOverview.js rename src/{engine.js => engine.jsx} (99%) create mode 100644 src/ui/React/CharacterOverview.jsx diff --git a/src/CharacterOverview.js b/src/CharacterOverview.js deleted file mode 100644 index 46f2ef916..000000000 --- a/src/CharacterOverview.js +++ /dev/null @@ -1,59 +0,0 @@ -import {Player} from "./Player"; - -import {numeralWrapper} from "./ui/numeralFormat"; - -function CharacterOverview() { - this.hp = document.getElementById("character-hp-text"); - this.money = document.getElementById("character-money-text"); - this.hack = document.getElementById("character-hack-text"); - this.str = document.getElementById("character-str-text"); - this.def = document.getElementById("character-def-text"); - this.dex = document.getElementById("character-dex-text"); - this.agi = document.getElementById("character-agi-text"); - this.cha = document.getElementById("character-cha-text"); - this.int = document.getElementById("character-int-text"); - this.intWrapper = document.getElementById("character-int-wrapper"); - this.repaintElem = document.getElementById("character-overview-text"); -} - -CharacterOverview.prototype.repaint = function() { - // this is an arbitrary function we can call to trigger a repaint. - this.repaintElem.getClientRects(); -} - -CharacterOverview.prototype.update = function() { - if (Player.hp == null) {Player.hp = Player.max_hp;} - - const replaceAndChanged = function(elem, text) { - if(elem.textContent === text) { - return false; - } - elem.textContent = text; - return true; - } - - let changed = false; - changed = replaceAndChanged(this.hp, Player.hp + " / " + Player.max_hp) || changed; - changed = replaceAndChanged(this.money, numeralWrapper.format(Player.money.toNumber(), '$0.000a')) || changed; - changed = replaceAndChanged(this.hack, (Player.hacking_skill).toLocaleString()) || changed; - changed = replaceAndChanged(this.str, (Player.strength).toLocaleString()) || changed; - changed = replaceAndChanged(this.def, (Player.defense).toLocaleString()) || changed; - changed = replaceAndChanged(this.dex, (Player.dexterity).toLocaleString()) || changed; - changed = replaceAndChanged(this.agi, (Player.agility).toLocaleString()) || changed; - changed = replaceAndChanged(this.cha, (Player.charisma).toLocaleString()) || changed; - changed = replaceAndChanged(this.int, (Player.intelligence).toLocaleString()) || changed; - - // handle int appearing - const int = this.intWrapper; - const old = int.style.display; - const now = Player.intelligence >= 1 ? "" : "none"; - if(old !== now) { - int.style.display = now; - changed = true; - } - - // recalculate box size if something changed - if(changed) this.repaint(); -} - -export {CharacterOverview}; diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx index e8d510de2..1a26bf92d 100644 --- a/src/Corporation/Corporation.jsx +++ b/src/Corporation/Corporation.jsx @@ -1523,7 +1523,7 @@ Employee.prototype.createUI = function(panel, corporation, industry) { } } - selector.addEventListener("change", ()=>{ + selector.addEventListener("change", () => { this.pos = selector.options[selector.selectedIndex].value; }); diff --git a/src/engine.js b/src/engine.jsx similarity index 99% rename from src/engine.js rename to src/engine.jsx index a2f2966b1..8b0646aa8 100644 --- a/src/engine.js +++ b/src/engine.jsx @@ -14,7 +14,7 @@ import { AugmentationNames } from "./Augmentation/dat import {BitNodes, initBitNodes, initBitNodeMultipliers} from "./BitNode/BitNode"; import {Bladeburner} from "./Bladeburner"; -import {CharacterOverview} from "./CharacterOverview"; +import { CharacterOverviewComponent } from "./ui/React/CharacterOverview"; import {cinematicTextFlag} from "./CinematicText"; import {generateRandomContract} from "./CodingContractGenerator"; import {CompanyPositions} from "./Company/CompanyPositions"; @@ -96,6 +96,8 @@ import { exceptionAlert } from "../utils/helpers/e import { removeLoadingScreen } from "../utils/uiHelpers/removeLoadingScreen"; import {KEY} from "../utils/helpers/keyCodes"; +import React from "react"; +import ReactDOM from "react-dom"; // These should really be imported with the module that is presenting that UI, but because they very much depend on the // cascade order, we'll pull them all in here. @@ -202,7 +204,6 @@ $(document).keydown(function(e) { const Engine = { version: "", Debug: true, - overview: new CharacterOverview(), //Clickable objects Clickables: { @@ -570,7 +571,7 @@ const Engine = { }, displayCharacterOverviewInfo: function() { - Engine.overview.update(); + ReactDOM.render(, document.getElementById('character-overview-text')); const save = document.getElementById("character-overview-save-button"); const flashClass = "flashing-button"; diff --git a/src/index.html b/src/index.html index b88c3e468..5dd72486b 100644 --- a/src/index.html +++ b/src/index.html @@ -796,35 +796,7 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hp:
Money: 
Hack: 
Str: 
Def: 
Dex: 
Agi: 
Cha: 
Int: 
+
diff --git a/src/ui/React/CharacterOverview.jsx b/src/ui/React/CharacterOverview.jsx new file mode 100644 index 000000000..1bdc69c9c --- /dev/null +++ b/src/ui/React/CharacterOverview.jsx @@ -0,0 +1,54 @@ +// Root React Component for the Corporation UI +import React from "react"; + +import { Player } from "../../Player"; +import { numeralWrapper } from "../../ui/numeralFormat"; + +const Component = React.Component; + +export class CharacterOverviewComponent extends Component { + render() { + let intelligence = ""; + if (Player.intelligence >= 1) { + intelligence=( + + Int: {(Player.intelligence).toLocaleString()} + +); + } + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + {intelligence} + +
Hp:{Player.hp + " / " + Player.max_hp}
Money: {numeralWrapper.format(Player.money.toNumber(), '$0.000a')}
Hack: {(Player.hacking_skill).toLocaleString()}
Str: {(Player.strength).toLocaleString()}
Def: {(Player.defense).toLocaleString()}
Dex: {(Player.dexterity).toLocaleString()}
Agi: {(Player.agility).toLocaleString()}
Cha: {(Player.charisma).toLocaleString()}
+
+ ) + } +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 5808115ad..324aa6e72 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -8,7 +8,7 @@ module.exports = (env, argv) => { const isDevelopment = argv.mode === 'development'; const outputDirectory = isDevServer ? "dist-dev" : "dist"; const entries = {}; - entries[`${outputDirectory}/engine`] = "./src/engine.js"; + entries[`${outputDirectory}/engine`] = "./src/engine.jsx"; if (!isDevServer) { entries["tests/tests"] = "./tests/index.js"; } From c3ecc189fdb86d36a0d604a45fcf3542c93fbbb8 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Fri, 15 Mar 2019 02:37:06 -0700 Subject: [PATCH 17/34] Fixed numerous bugs with the recent Corporation UI rewrite. Rebalanced Corporation mechanic to give employees better and more interesting effects --- README.md | 9 +- css/companymanagement.scss | 3 +- src/BitNode/BitNode.ts | 3 + src/Bladeburner.js | 2 +- src/Constants.ts | 8 + src/Corporation/Corporation.jsx | 157 +++++++++--------- src/Corporation/Product.ts | 10 +- src/Corporation/ResearchTree.ts | 4 +- src/Corporation/Warehouse.ts | 29 +++- src/Corporation/ui/CityTabs.jsx | 6 +- .../ui/CorporationUIEventHandler.js | 102 +++++++----- src/Corporation/ui/IndustryOffice.jsx | 78 ++++++--- src/Corporation/ui/IndustryOverview.jsx | 31 +++- src/Corporation/ui/IndustryWarehouse.jsx | 103 +++++++----- src/Corporation/ui/MainPanel.jsx | 11 ++ src/Corporation/ui/Overview.jsx | 2 +- src/Script/ScriptHelpers.js | 4 +- src/utils/calculateEffectWithFactors.ts | 45 +++++ utils/helpers/exceptionAlert.ts | 1 + 19 files changed, 394 insertions(+), 214 deletions(-) create mode 100644 src/utils/calculateEffectWithFactors.ts diff --git a/README.md b/README.md index fa82712e8..1275437ab 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ played at https://danielyxie.github.io/bitburner. # Documentation The game's official documentation can be found on [Read The Docs](http://bitburner.readthedocs.io/). Please note that this is still a -work-in-progress and is in its early stages. +work-in-progress. The documentation is created using [Sphinx](http://www.sphinx-doc.org). @@ -14,11 +14,6 @@ files](/doc/source) and then making a pull request with your contributions. For further guidance, please refer to the "As A Documentor" section of [CONTRIBUTING](CONTRIBUTING.md). -# Wiki -The game's wiki can be found on [Wikia](http://bitburner.wikia.com/). Please -note that the wiki is in the process of being deprecated. Eventually all of -the wiki content will be moved into the Read The Docs documentation. - # Contribution There are many ways to contribute to the game. It can be as simple as fixing a typo, correcting a bug, or improving the UI. For guidance on doing so, @@ -32,4 +27,4 @@ publish, and distribute your contributions to the project. A formal Contributor's License Agreement will be drawn up in the future. If you would like to make significant contributions to the project as a -collaborator, please reach out to @danielyxie to help coordinate the effort. \ No newline at end of file +collaborator, please reach out to @danielyxie to help coordinate the effort. diff --git a/css/companymanagement.scss b/css/companymanagement.scss index bd632773f..7a8944a2b 100644 --- a/css/companymanagement.scss +++ b/css/companymanagement.scss @@ -159,5 +159,6 @@ /* Research */ #corporation-research-popup-box-content { - overflow-x: visible !important; + overflow-x: auto !important; + overflow-y: auto !important; } diff --git a/src/BitNode/BitNode.ts b/src/BitNode/BitNode.ts index dbafb8f85..7ac23b188 100644 --- a/src/BitNode/BitNode.ts +++ b/src/BitNode/BitNode.ts @@ -258,6 +258,7 @@ export function initBitNodeMultipliers(p: IPlayer) { BitNodeMultipliers.FactionPassiveRepGain = 0; break; case 3: //Corporatocracy + BitNodeMultipliers.HackingLevelMultiplier = 0.8; BitNodeMultipliers.RepToDonateToFaction = 0.5; BitNodeMultipliers.AugmentationRepCost = 3; BitNodeMultipliers.AugmentationMoneyCost = 3; @@ -268,6 +269,8 @@ export function initBitNodeMultipliers(p: IPlayer) { BitNodeMultipliers.CompanyWorkMoney = 0.25; BitNodeMultipliers.CrimeMoney = 0.25; BitNodeMultipliers.HacknetNodeMoney = 0.25; + BitNodeMultipliers.HomeComputerRamCost = 1.5; + BitNodeMultipliers.PurchasedServerCost = 2; break; case 4: //The Singularity BitNodeMultipliers.ServerMaxMoney = 0.15; diff --git a/src/Bladeburner.js b/src/Bladeburner.js index 71357a0c6..dbf7677e3 100644 --- a/src/Bladeburner.js +++ b/src/Bladeburner.js @@ -1417,7 +1417,7 @@ Bladeburner.prototype.completeAction = function() { break; case ActionTypes["Hyperbolic Regeneration Chamber"]: Player.regenerateHp(HrcHpGain); - this.stamina = Math.max(this.maxStamina, this.stamina + HrcStaminaGain); // TODO Turn this into a const and adjust value + this.stamina = Math.min(this.maxStamina, this.stamina + HrcStaminaGain); this.startAction(this.action); if (this.logging.general) { this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${HrcStaminaGain} stamina`); diff --git a/src/Constants.ts b/src/Constants.ts index cb34aeb8a..33022df26 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -291,7 +291,15 @@ export let CONSTANTS: IMap = { ** Added several new Research upgrades ** Reduced the amount of Scientific Research needed to unlock the Hi-Tech R&D Laboratory from 10k to 5k ** Energy Material requirement of the Software industry reduced from 1 to 0.5 + ** It is now slightly easier to increase the Software industry's production multiplier ** Industries now have a maximum number of allowed products, starting at 3. This can be increased through research. + ** You can now see an approximation of how each material affects an industry's production multiplier by clicking the "?" help tip next to it + ** Significantly changed the effects of the different employee positions. See updated descriptions + ** Reduced the amount of money you gain from private investors + ** Training employees is now 3x more effective + + * Rebalanced BitNode-3 to make it slightly harder + * Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina ` } diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx index e8d510de2..a91189136 100644 --- a/src/Corporation/Corporation.jsx +++ b/src/Corporation/Corporation.jsx @@ -24,6 +24,8 @@ import { Player } from "../Player"; import { numeralWrapper } from "../ui/numeralFormat"; import { Page, routing } from "../ui/navigationTracking"; +import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors"; + import { dialogBoxCreate } from "../../utils/DialogBox"; import { clearSelector } from "../../utils/uiHelpers/clearSelector"; import { Reviver, @@ -37,7 +39,6 @@ import { formatNumber, generateRandomString } from "../../utils/String import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { isString } from "../../utils/helpers/isString"; import { KEY } from "../../utils/helpers/keyCodes"; -import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; import { removeElement } from "../../utils/uiHelpers/removeElement"; import { removeElementById } from "../../utils/uiHelpers/removeElementById"; import { yesNoBoxCreate, @@ -48,8 +49,7 @@ import { yesNoBoxCreate, yesNoTxtInpBoxGetNoButton, yesNoTxtInpBoxGetInput, yesNoBoxClose, - yesNoTxtInpBoxClose, - yesNoBoxOpen } from "../../utils/YesNoBox"; + yesNoTxtInpBoxClose } from "../../utils/YesNoBox"; // UI Related Imports import React from "react"; @@ -124,18 +124,6 @@ function Industry(params={}) { [Locations.Volhaven]: 0 }; - this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location - [Locations.Aevum]: 0, - [Locations.Chonqing]: 0, - [Locations.Sector12]: new Warehouse({ - loc:Locations.Sector12, - size: WarehouseInitialSize, - }), - [Locations.NewTokyo]: 0, - [Locations.Ishima]: 0, - [Locations.Volhaven]: 0 - }; - this.name = params.name ? params.name : 0; this.type = params.type ? params.type : 0; @@ -183,6 +171,20 @@ function Industry(params={}) { this.state = "START"; this.newInd = true; + this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location + [Locations.Aevum]: 0, + [Locations.Chonqing]: 0, + [Locations.Sector12]: new Warehouse({ + corp: params.corp, + industry: this, + loc: Locations.Sector12, + size: WarehouseInitialSize, + }), + [Locations.NewTokyo]: 0, + [Locations.Ishima]: 0, + [Locations.Volhaven]: 0 + }; + this.init(); } @@ -340,8 +342,8 @@ Industry.prototype.init = function() { this.sciFac = 0.62; this.advFac = 0.16; this.hwFac = 0.25; - this.reFac = 0.1; - this.aiFac = 0.15; + this.reFac = 0.15; + this.aiFac = 0.18; this.robFac = 0.05; this.reqMats = { "Hardware": 0.5, @@ -711,7 +713,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { for (var j = 0; j < this.prodMats.length; ++j) { warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac); warehouse.materials[this.prodMats[j]].qlt = - (office.employeeProd[EmployeePositions.Engineer] / 100 + + (office.employeeProd[EmployeePositions.Engineer] / 90 + Math.pow(this.sciResearch.qty, this.sciFac) + Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3); } @@ -918,27 +920,30 @@ Industry.prototype.processProducts = function(marketCycles=1, corporation) { //Create products if (this.state === "PRODUCTION") { - for (var prodName in this.products) { - if (this.products.hasOwnProperty(prodName)) { - var prod = this.products[prodName]; - if (!prod.fin) { - var city = prod.createCity, office = this.offices[city]; - var total = office.employeeProd[EmployeePositions.Operations] + - office.employeeProd[EmployeePositions.Engineer] + - office.employeeProd[EmployeePositions.Management], ratio; - if (total === 0) { - ratio = 0; - } else { - ratio = office.employeeProd[EmployeePositions.Engineer] / total + - office.employeeProd[EmployeePositions.Operations] / total + - office.employeeProd[EmployeePositions.Management] / total; - } - prod.createProduct(marketCycles, ratio * Math.pow(total, 0.35)); - if (prod.prog >= 100) { - prod.finishProduct(office.employeeProd, this); - } - break; + for (const prodName in this.products) { + const prod = this.products[prodName]; + if (!prod.fin) { + const city = prod.createCity; + const office = this.offices[city]; + + // Designing/Creating a Product is based mostly off Engineers + const engrProd = office.employeeProd[EmployeePositions.Engineer]; + const mgmtProd = office.employeeProd[EmployeePositions.Management]; + const opProd = office.employeeProd[EmployeePositions.Operations]; + const total = engrProd + mgmtProd + opProd; + + if (total <= 0) { break; } + + // Management is a multiplier for the production from Engineers + const mgmtFactor = 1 + (mgmtProd / (1.2 * total)); + + const progress = (Math.pow(engrProd, 0.34) + Math.pow(opProd, 0.2)) * mgmtFactor; + + prod.createProduct(marketCycles, progress); + if (prod.prog >= 100) { + prod.finishProduct(office.employeeProd, this); } + break; } } } @@ -1147,33 +1152,38 @@ Industry.prototype.upgrade = function(upgrade, refs) { } } -//Returns how much of a material can be produced based of office productivity (employee stats) +// Returns how much of a material can be produced based of office productivity (employee stats) Industry.prototype.getOfficeProductivity = function(office, params) { - var total = office.employeeProd[EmployeePositions.Operations] + - office.employeeProd[EmployeePositions.Engineer] + - office.employeeProd[EmployeePositions.Management], ratio; - if (total === 0) { - ratio = 0; - } else { - ratio = (office.employeeProd[EmployeePositions.Operations] / total) * - (office.employeeProd[EmployeePositions.Engineer] / total) * - (office.employeeProd[EmployeePositions.Management] / total); - ratio = Math.max(0.01, ratio); //Minimum ratio value if you have employees - } + const opProd = office.employeeProd[EmployeePositions.Operations]; + const engrProd = office.employeeProd[EmployeePositions.Engineer]; + const mgmtProd = office.employeeProd[EmployeePositions.Management] + const total = opProd + engrProd + mgmtProd; + + if (total <= 0) { return 0; } + + // Management is a multiplier for the production from Operations and Engineers + const mgmtFactor = 1 + (mgmtProd / (1.2 * total)); + + // For production, Operations is slightly more important than engineering + // Both Engineering and Operations have diminishing returns + const prod = (Math.pow(opProd, 0.4) + Math.pow(engrProd, 0.3)) * mgmtFactor; + + // Generic multiplier for the production. Used for game-balancing purposes + const balancingMult = 0.05; + if (params && params.forProduct) { - return ratio * Math.pow(total, 0.25); + // Products are harder to create and therefore have less production + return 0.5 * balancingMult * prod; } else { - return 2 * ratio * Math.pow(total, 0.35); + return balancingMult * prod; } } -//Returns a multiplier based on the office' 'Business' employees that affects sales +// Returns a multiplier based on the office' 'Business' employees that affects sales Industry.prototype.getBusinessFactor = function(office) { - var ratioMult = 1; - if (office.employeeProd["total"] > 0) { - ratioMult = 1 + (office.employeeProd[EmployeePositions.Business] / office.employeeProd["total"]); - } - return ratioMult * Math.pow(1 + office.employeeProd[EmployeePositions.Business], 0.25); + const businessProd = 1 + office.employeeProd[EmployeePositions.Business]; + + return calculateEffectWithFactors(businessProd, 0.26, 10e3); } //Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This @@ -1399,7 +1409,7 @@ function Employee(params={}) { //Returns the amount the employee needs to be paid Employee.prototype.process = function(marketCycles=1, office) { - var gain = 0.001 * marketCycles, + var gain = 0.003 * marketCycles, det = gain * Math.random(); this.age += gain; this.exp += gain; @@ -1425,16 +1435,9 @@ Employee.prototype.process = function(marketCycles=1, office) { this.eff += trainingEff; } - //Weight based on how full office is - //Too many employees = more likely to decrease energy and happiness - var officeCapacityWeight = 0.5 * (office.employees.length / office.size - 0.5); - if (Math.random() < 0.5 - officeCapacityWeight) { - this.ene += det; - this.hap += det; - } else { - this.ene -= det; - this.hap -= det; - } + this.ene -= det; + this.hap -= det; + if (this.ene < office.minEne) {this.ene = office.minEne;} if (this.hap < office.minHap) {this.hap = office.minHap;} var salary = this.sal * marketCycles * SecsPerMarketCycle; @@ -1640,18 +1643,16 @@ OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) { salaryPaid += salary; } - this.calculateEmployeeProductivity(marketCycles, parentRefs); + this.calculateEmployeeProductivity(parentRefs); return salaryPaid; } -OfficeSpace.prototype.calculateEmployeeProductivity = function(marketCycles=1, parentRefs) { +OfficeSpace.prototype.calculateEmployeeProductivity = function(parentRefs) { var company = parentRefs.corporation, industry = parentRefs.industry; //Reset for (const name in this.employeeProd) { - if (this.employeeProd.hasOwnProperty(name)) { - this.employeeProd[name] = 0; - } + this.employeeProd[name] = 0; } var total = 0; @@ -1977,19 +1978,19 @@ Corporation.prototype.getInvestment = function() { switch (this.fundingRound) { case 0: //Seed percShares = 0.10; - roundMultiplier = 5; + roundMultiplier = 4; break; case 1: //Series A percShares = 0.35; - roundMultiplier = 4; + roundMultiplier = 3; break; case 2: //Series B percShares = 0.25; - roundMultiplier = 4; + roundMultiplier = 3; break; case 3: //Series C percShares = 0.20; - roundMultiplier = 3.5; + roundMultiplier = 2.5; break; case 4: return; diff --git a/src/Corporation/Product.ts b/src/Corporation/Product.ts index 6f8524e62..2a7d6afb1 100644 --- a/src/Corporation/Product.ts +++ b/src/Corporation/Product.ts @@ -164,11 +164,11 @@ export class Product { //Calculate properties var progrMult = this.prog / 100; - var engrRatio = employeeProd[EmployeePositions.Engineer] / employeeProd["total"], - mgmtRatio = employeeProd[EmployeePositions.Management] / employeeProd["total"], - rndRatio = employeeProd[EmployeePositions.RandD] / employeeProd["total"], - opsRatio = employeeProd[EmployeePositions.Operations] / employeeProd["total"], - busRatio = employeeProd[EmployeePositions.Business] / employeeProd["total"]; + const engrRatio = employeeProd[EmployeePositions.Engineer] / employeeProd["total"]; + const mgmtRatio = employeeProd[EmployeePositions.Management] / employeeProd["total"]; + const rndRatio = employeeProd[EmployeePositions.RandD] / employeeProd["total"]; + const opsRatio = employeeProd[EmployeePositions.Operations] / employeeProd["total"]; + const busRatio = employeeProd[EmployeePositions.Business] / employeeProd["total"]; var designMult = 1 + (Math.pow(this.designCost, 0.1) / 100); console.log("designMult: " + designMult); var balanceMult = (1.2 * engrRatio) + (0.9 * mgmtRatio) + (1.3 * rndRatio) + diff --git a/src/Corporation/ResearchTree.ts b/src/Corporation/ResearchTree.ts index 9cbdd8afa..8414631c8 100644 --- a/src/Corporation/ResearchTree.ts +++ b/src/Corporation/ResearchTree.ts @@ -8,6 +8,8 @@ import { ResearchMap } from "./ResearchMap"; import { IMap } from "../types"; +import { numeralWrapper } from "../ui/numeralFormat"; + interface IConstructorParams { children?: Node[]; cost: number; @@ -83,7 +85,7 @@ export class Node { children: childrenArray, HTMLclass: htmlClass, innerHTML: `
` + - `${this.text}
${this.cost} Scientific Research` + + `${this.text}
${numeralWrapper.format(this.cost, "0,0")} Scientific Research` + `` + `${research.desc}` + `` + diff --git a/src/Corporation/Warehouse.ts b/src/Corporation/Warehouse.ts index 6f7a8cdd3..ecbaa826a 100644 --- a/src/Corporation/Warehouse.ts +++ b/src/Corporation/Warehouse.ts @@ -5,16 +5,19 @@ import { numeralWrapper } from "../ui/numeralFormat"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; - -interface IConstructorParams { - loc?: string; - size?: number; -} +import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; interface IParent { getStorageMultiplier(): number; } +interface IConstructorParams { + corp?: IParent; + industry?: IParent; + loc?: string; + size?: number; +} + export class Warehouse { // Initiatizes a Warehouse object from a JSON save state. static fromJSON(value: any): Warehouse { @@ -65,6 +68,10 @@ export class Warehouse { AICores: new Material({name: "AI Cores"}), RealEstate: new Material({name: "Real Estate"}) } + + if (params.corp && params.industry) { + this.updateSize(params.corp, params.industry); + } } // Re-calculate how much space is being used by this Warehouse @@ -76,7 +83,7 @@ export class Warehouse { if (MaterialSizes.hasOwnProperty(matName)) { this.sizeUsed += (mat.qty * MaterialSizes[matName]); if (mat.qty > 0) { - this.breakdown += (matName + ": " + numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0") + "
"); + this.breakdown += (matName + ": " + numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0.0") + "
"); } } } @@ -86,9 +93,13 @@ export class Warehouse { } updateSize(corporation: IParent, industry: IParent) { - this.size = (this.level * 100) - * corporation.getStorageMultiplier() - * industry.getStorageMultiplier(); + try { + this.size = (this.level * 100) + * corporation.getStorageMultiplier() + * industry.getStorageMultiplier(); + } catch(e) { + exceptionAlert(e); + } } // Serialize the current object to a JSON save state. diff --git a/src/Corporation/ui/CityTabs.jsx b/src/Corporation/ui/CityTabs.jsx index 0172a6802..56dfbf27b 100644 --- a/src/Corporation/ui/CityTabs.jsx +++ b/src/Corporation/ui/CityTabs.jsx @@ -14,6 +14,9 @@ export class CityTabs extends BaseReactComponent { if (props.city == null) { throw new Error(`CityTabs component constructed without 'city' property`) } + if (props.cityStateSetter == null) { + throw new Error(`CityTabs component constructed without 'cityStateSetter' property`) + } super(props); } @@ -46,7 +49,8 @@ export class CityTabs extends BaseReactComponent { } // Tab to "Expand into new City" - const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler(), division); + const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler(), division, this.props.cityStateSetter); + tabs.push(this.renderTab({ current: false, key: "Expand into new City", diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js index edbd70d56..6feec0808 100644 --- a/src/Corporation/ui/CorporationUIEventHandler.js +++ b/src/Corporation/ui/CorporationUIEventHandler.js @@ -17,6 +17,8 @@ import { Industries, IndustryDescriptions, IndustryResearchTrees } from "../IndustryData"; +import { Product } from "../Product"; + import { Player } from "../../Player"; import { numeralWrapper } from "../../ui/numeralFormat"; @@ -109,7 +111,7 @@ export class CorporationEventHandler { } } }); - var confirmButton = createElement("a", { + var confirmButton = createElement("button", { class:"a-link-button", innerText:"Bribe", display:"inline-block", clickListener:()=>{ var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); @@ -181,7 +183,7 @@ export class CorporationEventHandler { } } }); - var confirmBtn = createElement("a", { + var confirmBtn = createElement("button", { class:"a-link-button", innerText:"Buy shares", display:"inline-block", clickListener: () => { var shares = Math.round(input.value); @@ -234,12 +236,12 @@ export class CorporationEventHandler { "produce this product and all of its existing stock will be " + "removed and left unsold", }); - const confirmBtn = createElement("a", { + const confirmBtn = createElement("button", { class:"a-link-button",innerText:"Discontinue", - clickListener:()=>{ + clickListener: () => { industry.discontinueProduct(product, parentRefs); removeElementById(popupId); - this.corp.rerender(); + this.rerender(); return false; } }); @@ -294,7 +296,7 @@ export class CorporationEventHandler { placeholder:"Export amount / s" }); - const exportBtn = createElement("a", { + const exportBtn = createElement("button", { class: "std-button", display:"inline-block", innerText:"Export", clickListener: () => { const industryName = getSelectText(industrySelector); @@ -473,7 +475,7 @@ export class CorporationEventHandler { } }); - issueBtn = createElement("a", { + issueBtn = createElement("button", { class: "std-button", display: "inline-block", innerText: "Issue New Shares", @@ -529,7 +531,7 @@ export class CorporationEventHandler { } // Create a popup that lets the player limit the production of a product - createLimitProductProdutionPopup(product) { + createLimitProductProdutionPopup(product, city) { const popupId = "cmpy-mgmt-limit-product-production-popup"; const txt = createElement("p", { innerText:"Enter a limit to the amount of this product you would " + @@ -543,7 +545,7 @@ export class CorporationEventHandler { if (e.keyCode === KEY.ENTER) { confirmBtn.click(); } } }); - confirmBtn = createElement("a", { + confirmBtn = createElement("button", { class: "std-button", display:"inline-block", innerText:"Limit production", @@ -585,7 +587,7 @@ export class CorporationEventHandler { const txt = createElement("p", { innerHTML: popupText, }); - const designCity = createElement("select"); + const designCity = createElement("select", { margin: "5px" }); for (const cityName in division.offices) { if (division.offices[cityName] instanceof OfficeSpace) { designCity.add(createElement("option", { @@ -603,18 +605,26 @@ export class CorporationEventHandler { productNamePlaceholder = "Property Name"; } var productNameInput = createElement("input", { + margin: "5px", placeholder: productNamePlaceholder, }); var lineBreak1 = createElement("br"); var designInvestInput = createElement("input", { + margin: "5px", + placeholder: "Design investment", type: "number", - placeholder: "Design investment" }); + let confirmBtn; var marketingInvestInput = createElement("input", { + margin: "5px", + placeholder: "Marketing investment", type: "number", - placeholder: "Marketing investment" + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) { confirmBtn.click(); } + } }); - const confirmBtn = createElement("a", { + confirmBtn = createElement("button", { class: "std-button", innerText: "Develop Product", clickListener: () => { @@ -637,17 +647,19 @@ export class CorporationEventHandler { designCost: designInvest, advCost: marketingInvest, }); + if (division.products[product.name] instanceof Product) { + dialogBoxCreate(`You already have a product with this name!`); + return; + } this.corp.funds = this.corp.funds.minus(designInvest + marketingInvest); division.products[product.name] = product; removeElementById(popupId); } this.rerender(); - //this.updateUIContent(); - //this.displayDivisionContent(division, city); return false; } }) - const cancelBtn = createPopupCloseButton(popupid, { + const cancelBtn = createPopupCloseButton(popupId, { class: "std-button", innerText: "Cancel", }); @@ -741,7 +753,8 @@ export class CorporationEventHandler { } // Create a popup that lets the player expand into a new city (for the current industry) - createNewCityPopup(division) { + // The 'cityStateSetter' arg is a function that sets the UI's 'city' state property + createNewCityPopup(division, cityStateSetter) { const popupId = "cmpy-mgmt-expand-city-popup"; const text = createElement("p", { innerText: "Would you like to expand into a new city by opening an office? " + @@ -757,7 +770,7 @@ export class CorporationEventHandler { } } - const confirmBtn = createElement("a", { + const confirmBtn = createElement("button", { class:"std-button", display:"inline-block", innerText: "Confirm", @@ -772,9 +785,11 @@ export class CorporationEventHandler { loc: city, size: OfficeInitialSize, }); - this.corp.displayDivisionContent(division, city); } + + cityStateSetter(city); removeElementById(popupId); + this.rerender(); return false; } }); @@ -836,15 +851,17 @@ export class CorporationEventHandler { } else { this.corp.funds = this.corp.funds.minus(IndustryStartingCosts[ind]); var newInd = new Industry({ - name:newDivisionName, - type:ind, + corp: this.corp, + name: newDivisionName, + type: ind, }); this.corp.divisions.push(newInd); - // this.corp.updateUIHeaderTabs(); - // this.corp.selectHeaderTab(headerTabs[headerTabs.length-2]); + + // Set routing to the new division so that the UI automatically switches to it + this.routing.routeTo(newDivisionName); + removeElementById("cmpy-mgmt-expand-industry-popup"); this.rerender(); - // this.corp.displayDivisionContent(newInd, Locations.Sector12); } return false; } @@ -855,14 +872,14 @@ export class CorporationEventHandler { innerText: "Cancel", }); - //Make an object to keep track of what industries you're already in + // Make an object to keep track of what industries you're already in const ownedIndustries = {}; for (let i = 0; i < this.corp.divisions.length; ++i) { ownedIndustries[this.corp.divisions[i].type] = true; } - //Add industry types to selector - //Have Agriculture be first as recommended option + // Add industry types to selector + // Have Agriculture be first as recommended option if (!ownedIndustries["Agriculture"]) { selector.add(createElement("option", { text:Industries["Agriculture"], value:"Agriculture" @@ -933,6 +950,7 @@ export class CorporationEventHandler { mat.buy = parseFloat(input.value); if (isNaN(mat.buy)) {mat.buy = 0;} removeElementById(purchasePopupId); + this.rerender(); return false; } } @@ -942,6 +960,7 @@ export class CorporationEventHandler { clickListener: () => { mat.buy = 0; removeElementById(purchasePopupId); + this.rerender(); return false; } }); @@ -979,7 +998,7 @@ export class CorporationEventHandler { type: "number", onkeyup: (e) => { e.preventDefault(); - bulkPurchaseUpdateCostTxt(); + updateBulkPurchaseText(); if (e.keyCode === KEY.ENTER) {bulkPurchaseConfirmBtn.click();} } }); @@ -1010,6 +1029,7 @@ export class CorporationEventHandler { elems.push(bulkPurchaseInfo); elems.push(bulkPurchaseCostTxt); elems.push(bulkPurchaseInput); + elems.push(bulkPurchaseConfirmBtn); } createPopup(purchasePopupId, elems); @@ -1129,7 +1149,7 @@ export class CorporationEventHandler { } // Create a popup that lets the player manage sales of the product - createSellProductPopup(product) { + createSellProductPopup(product, city) { const popupId = "cmpy-mgmt-sell-product-popup"; const txt = createElement("p", { innerHTML:"Enter the maximum amount of " + product.name + " you would like " + @@ -1150,15 +1170,17 @@ export class CorporationEventHandler { }); let confirmBtn; const inputQty = createElement("input", { + margin: "5px 0px 5px 0px", placeholder: "Sell amount", type: "text", - value:product.sllman[city][1] ? product.sllman[city][1] : null, + value: product.sllman[city][1] ? product.sllman[city][1] : null, onkeyup: (e) => { e.preventDefault(); if (e.keyCode === KEY.ENTER) {confirmBtn.click();} } }); const inputPx = createElement("input", { + margin: "5px 0px 5px 0px", placeholder: "Sell price", type: "text", value: product.sCost ? product.sCost : null, @@ -1167,7 +1189,7 @@ export class CorporationEventHandler { if (e.keyCode === KEY.ENTER) {confirmBtn.click();} } }); - confirmBtn = createElement("a", { + confirmBtn = createElement("button", { class: "std-button", innerText: "Confirm", clickListener: () => { @@ -1276,7 +1298,7 @@ export class CorporationEventHandler { } } }); - const confirmBtn = createElement("a", { + const confirmBtn = createElement("button", { class:"a-link-button", innerText:"Sell shares", display:"inline-block", clickListener:()=>{ var shares = Math.round(input.value); @@ -1355,7 +1377,7 @@ export class CorporationEventHandler { if (e.keyCode === KEY.ENTER) {confirmBtn.click();} } }); - confirmBtn = createElement("a", { + confirmBtn = createElement("button", { class: "std-button", innerText: "Throw Party", clickListener:()=>{ @@ -1366,20 +1388,20 @@ export class CorporationEventHandler { if (this.corp.funds.lt(totalCost)) { dialogBoxCreate("You don't have enough company funds to throw this.corp party!"); } else { - this.corp.funds = this.funds.minus(totalCost); + this.corp.funds = this.corp.funds.minus(totalCost); var mult; for (let fooit = 0; fooit < office.employees.length; ++fooit) { mult = office.employees[fooit].throwParty(input.value); } dialogBoxCreate("You threw a party for the office! The morale and happiness " + - "of each employee increased by " + formatNumber((mult-1) * 100, 2) + "%."); + "of each employee increased by " + numeralWrapper.formatPercentage((mult-1))); removeElementById(popupId); } } return false; } }); - const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + const cancelBtn = createPopupCloseButton(popupId, { class: "std-button", innerText: "Cancel" }); createPopup(popupId, [txt, totalCostTxt, input, confirmBtn, cancelBtn]); input.focus(); @@ -1420,7 +1442,7 @@ export class CorporationEventHandler { }); const text2 = createElement("p", { innerText: "Upgrade size: " }); - const confirmBtn = createElement("a", { + const confirmBtn = createElement("button", { class: this.corp.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button", display:"inline-block", margin:"4px", innerText:"by 3", tooltip:numeralWrapper.format(upgradeCost, "$0.000a"), @@ -1437,7 +1459,7 @@ export class CorporationEventHandler { return false; } }); - const confirmBtn15 = createElement("a", { + const confirmBtn15 = createElement("button", { class: this.corp.funds.lt(upgradeCost15) ? "a-link-button-inactive" : "a-link-button", display:"inline-block", margin:"4px", innerText:"by 15", tooltip:numeralWrapper.format(upgradeCost15, "$0.000a"), @@ -1454,7 +1476,7 @@ export class CorporationEventHandler { return false; } }); - const confirmBtnMax = createElement("a", { + const confirmBtnMax = createElement("button", { class:this.corp.funds.lt(upgradeCostMax) ? "a-link-button-inactive" : "a-link-button", display:"inline-block", margin:"4px", innerText:"by MAX (" + maxNum*OfficeInitialSize + ")", tooltip:numeralWrapper.format(upgradeCostMax, "$0.000a"), @@ -1484,6 +1506,8 @@ export class CorporationEventHandler { dialogBoxCreate("You do not have enough funds to do this!"); } else { division.warehouses[city] = new Warehouse({ + corp: corp, + industry: division, loc: city, size: WarehouseInitialSize, }); diff --git a/src/Corporation/ui/IndustryOffice.jsx b/src/Corporation/ui/IndustryOffice.jsx index 566d44588..91d234a80 100644 --- a/src/Corporation/ui/IndustryOffice.jsx +++ b/src/Corporation/ui/IndustryOffice.jsx @@ -15,6 +15,8 @@ export class IndustryOffice extends BaseReactComponent { super(props); this.state = { + city: "", + division: "", employeeManualAssignMode: false, employee: null, // Reference to employee being referenced if in Manual Mode numEmployees: 0, @@ -30,6 +32,17 @@ export class IndustryOffice extends BaseReactComponent { this.updateEmployeeCount(); // This function validates division and office refs } + resetEmployeeCount() { + this.state.numEmployees = 0; + this.state.numOperations = 0; + this.state.numEngineers = 0; + this.state.numBusiness = 0; + this.state.numManagement = 0; + this.state.numResearch = 0; + this.state.numUnassigned = 0; + this.state.numTraining = 0; + } + updateEmployeeCount() { const division = this.routing().currentDivision; if (division == null) { @@ -40,6 +53,13 @@ export class IndustryOffice extends BaseReactComponent { throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`); } + // If we're in a new city, we have to reset the state + if (division.name !== this.state.division || this.props.currentCity !== this.state.city) { + this.resetEmployeeCount(); + this.state.division = division.name; + this.state.city = this.props.currentCity; + } + // Calculate how many NEW emplyoees we need to account for const currentNumEmployees = office.employees.length; const newEmployees = currentNumEmployees - this.state.numEmployees; @@ -151,6 +171,7 @@ export class IndustryOffice extends BaseReactComponent { --this.state.numUnassigned; office.assignEmployeeToJob(to); + office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division }); this.corp().rerender(); } @@ -194,6 +215,7 @@ export class IndustryOffice extends BaseReactComponent { ++this.state.numUnassigned; office.unassignEmployeeFromJob(from); + office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division }); this.corp().rerender(); } @@ -283,36 +305,40 @@ export class IndustryOffice extends BaseReactComponent {

Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}

{ vechain && -
-

+

Material Production: {numeralWrapper.format(division.getOfficeProductivity(office), "0.000")} The base amount of material this office can produce. Does not include production multipliers from upgrades and materials. This value is based off the productivity of your Operations, Engineering, and Management employees -


-

+

+ } + { + vechain && +

Product Production: {numeralWrapper.format(division.getOfficeProductivity(office, {forProduct:true}), "0.000")} The base amount of any given Product this office can produce. Does not include production multipliers from upgrades and materials. This value is based off the productivity of your Operations, Engineering, and Management employees -


-

- Business Multiplier: x" ${numeralWrapper.format(division.getBusinessFactor(office), "0.000")} +

+ } + { + vechain && +

+ Business Multiplier: x{numeralWrapper.format(division.getBusinessFactor(office), "0.000")} The effect this office's 'Business' employees has on boosting sales -


-
+

}

{EmployeePositions.Operations} ({this.state.numOperations}) - Manages supply chain operations. Improves production. + Manages supply chain operations. Improves the amount of Materials and Products you produce.

@@ -322,7 +348,9 @@ export class IndustryOffice extends BaseReactComponent {

{EmployeePositions.Engineer} ({this.state.numEngineers}) - Develops and maintains products and production systems. Improves production. + Develops and maintains products and production systems. Increases the quality of + everything you produce. Also increases the amount you produce (not as much + as Operations, however)

@@ -332,7 +360,7 @@ export class IndustryOffice extends BaseReactComponent {

{EmployeePositions.Business} ({this.state.numBusiness}) - Handles sales and finances. Improves sales. + Handles sales and finances. Improves the amount of Materials and Products you can sell.

@@ -342,7 +370,8 @@ export class IndustryOffice extends BaseReactComponent {

{EmployeePositions.Management} ({this.state.numManagement}) - Leads and oversees employees and office operations. Improves production. + Leads and oversees employees and office operations. Improves the effectiveness of + Engineer and Operations employees

@@ -398,27 +427,36 @@ export class IndustryOffice extends BaseReactComponent { for (let i = 0; i < office.employees.length; ++i) { if (name === office.employees[i].name) { this.state.employee = office.employees[i]; + break; } } + + corp.rerender(); } // Employee Positions Selector + const emp = this.state.employee; + let employeePositionSelectorInitialValue = null; const employeePositions = []; const positionNames = Object.values(EmployeePositions); for (let i = 0; i < positionNames.length; ++i) { - employeePositions.push(); + employeePositions.push(); + if (emp != null && emp.pos === positionNames[i]) { + employeePositionSelectorInitialValue = positionNames[i]; + } } const employeePositionSelectorOnChange = (e) => { const pos = getSelectText(e.target); this.state.employee.pos = pos; + this.resetEmployeeCount(); + corp.rerender(); } // Numeraljs formatter const nf = "0.000"; // Employee stats (after applying multipliers) - const emp = this.state.employee; const effCre = emp ? emp.cre * corp.getEmployeeCreMultiplier() * division.getEmployeeCreMultiplier() : 0; const effCha = emp ? emp.cha * corp.getEmployeeChaMultiplier() * division.getEmployeeChaMultiplier() : 0; const effInt = emp ? emp.int * corp.getEmployeeIntMultiplier() * division.getEmployeeIntMultiplier() : 0; @@ -436,6 +474,9 @@ export class IndustryOffice extends BaseReactComponent {
+ { this.state.employee != null &&

@@ -462,15 +503,11 @@ export class IndustryOffice extends BaseReactComponent { } { this.state.employee != null && - {employeePositions} }

- -
) } @@ -495,7 +532,6 @@ export class IndustryOffice extends BaseReactComponent { } } - const hireEmployeeButtonOnClick = () => { office.findEmployees({ corporation: corp, industry: division }); } diff --git a/src/Corporation/ui/IndustryOverview.jsx b/src/Corporation/ui/IndustryOverview.jsx index f13e5106b..a108fba6d 100644 --- a/src/Corporation/ui/IndustryOverview.jsx +++ b/src/Corporation/ui/IndustryOverview.jsx @@ -8,6 +8,7 @@ import { Industries } from "../IndustryData"; import { IndustryUpgrades } from "../IndustryUpgrades"; import { numeralWrapper } from "../../ui/numeralFormat"; import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; export class IndustryOverview extends BaseReactComponent { renderMakeProductButton() { @@ -109,6 +110,16 @@ export class IndustryOverview extends BaseReactComponent { const profitStr = `Profit: ${numeralWrapper.formatMoney(profit)} / s`; const productionMultHelpTipOnClick = () => { + // Wrapper for createProgressBarText() + // Converts the industry's "effectiveness factors" + // into a graphic (string) depicting how high that effectiveness is + function convertEffectFacToGraphic(fac) { + return createProgressBarText({ + progress: fac, + totalTicks: 20, + }); + } + dialogBoxCreate("Owning Hardware, Robots, AI Cores, and Real Estate " + "can boost your Industry's production. The effect these " + "materials have on your production varies between Industries. " + @@ -118,7 +129,13 @@ export class IndustryOverview extends BaseReactComponent { "the individual production multiplier of each of its office locations. " + "This production multiplier is applied to each office. Therefore, it is " + "beneficial to expand into new cities as this can greatly increase the " + - "production multiplier of your entire Division."); + "production multiplier of your entire Division.

" + + "Below are approximations for how effective each material is at boosting " + + "this industry's production multiplier (Bigger bars = more effective):

" + + `Hardware:    ${convertEffectFacToGraphic(division.hwFac)}
` + + `Robots:      ${convertEffectFacToGraphic(division.robFac)}
` + + `AI Cores:    ${convertEffectFacToGraphic(division.aiFac)}
` + + `Real Estate: ${convertEffectFacToGraphic(division.reFac)}`); } return ( @@ -129,15 +146,15 @@ export class IndustryOverview extends BaseReactComponent { {popularity}
{ (advertisingInfo !== false) && -

Advertising Multiplier: {numeralWrapper.format(totalAdvertisingFac, "0.000")} +

Advertising Multiplier: x{numeralWrapper.format(totalAdvertisingFac, "0.000")} Total multiplier for this industrys sales due to its awareness and popularity
- Awareness Bonus: x{formatNumber(Math.pow(awarenessFac, 0.85), 3)} + Awareness Bonus: x{numeralWrapper.format(Math.pow(awarenessFac, 0.85), "0.000")}
- Popularity Bonus: x{formatNumber(Math.pow(popularityFac, 0.85), 3)} + Popularity Bonus: x{numeralWrapper.format(Math.pow(popularityFac, 0.85), "0.000")}
- Ratio Multiplier: x{formatNumber(Math.pow(ratioFac, 0.85), 3)} + Ratio Multiplier: x{numeralWrapper.format(Math.pow(ratioFac, 0.85), "0.000")}

} @@ -157,7 +174,7 @@ export class IndustryOverview extends BaseReactComponent {
?


- Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000")} + Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000a")} Scientific Research increases the quality of the materials and products that you produce. @@ -252,7 +269,7 @@ export class IndustryOverview extends BaseReactComponent { { division.makesProducts && - {makeProductButton} + makeProductButton }

) diff --git a/src/Corporation/ui/IndustryWarehouse.jsx b/src/Corporation/ui/IndustryWarehouse.jsx index 421ff5303..e13bb3cf7 100644 --- a/src/Corporation/ui/IndustryWarehouse.jsx +++ b/src/Corporation/ui/IndustryWarehouse.jsx @@ -1,38 +1,50 @@ // React Component for displaying an Industry's warehouse information // (right-side panel in the Industry UI) import React from "react"; -import { BaseReactComponent } from "./BaseReactComponent"; +import { BaseReactComponent } from "./BaseReactComponent"; -import { Material } from "../Material"; -import { Product } from "../Product"; +import { Material } from "../Material"; +import { Product } from "../Product"; import { Warehouse, WarehouseInitialCost, - WarehouseUpgradeBaseCost } from "../Corporation"; + WarehouseUpgradeBaseCost, + ProductProductionCostRatio } from "../Corporation"; -import { numeralWrapper } from "../../ui/numeralFormat"; +import { numeralWrapper } from "../../ui/numeralFormat"; -import { isString } from "../../../utils/helpers/isString"; +import { isString } from "../../../utils/helpers/isString"; // Creates the UI for a single Product type function ProductComponent(props) { const corp = props.corp; const division = props.division; const warehouse = props.warehouse; + const city = props.city; const product = props.product; const eventHandler = props.eventHandler; - const nf = "0.000"; // Numeraljs formatter + // Numeraljs formatters + const nf = "0.000"; + const nfB = "0.000a"; // For numbers that might be big const hasUpgradeDashboard = division.hasResearch("uPgrade: Dashboard"); // Total product gain = production - sale - const totalGain = totalGain = product.data[city][1] - product.data[city][2]; + const totalGain = product.data[city][1] - product.data[city][2]; // Sell button - const sellButtonText = product.sllman[city][1] === -1 - ? "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/MAX)" - : "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/" + numeralWrapper.format(product.sllman[city][1], nf) + ")"; + let sellButtonText; + if (product.sllman[city][0]) { + if (isString(product.sllman[city][1])) { + sellButtonText = `Sell (${numeralWrapper.format(product.data[city][2], nfB)}/${product.sllman[city][1]})`; + } else { + sellButtonText = `Sell (${numeralWrapper.format(product.data[city][2], nfB)}/${numeralWrapper.format(product.sllman[city][1], nfB)})`; + } + } else { + sellButtonText = "Sell (0.000/0.000)"; + } + if (product.sCost) { if (isString(product.sCost)) { sellButtonText += (" @ " + product.sCost); @@ -40,14 +52,14 @@ function ProductComponent(props) { sellButtonText += (" @ " + numeralWrapper.format(product.sCost, "$0.000a")); } } - const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product); + const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product, city); // Limit Production button const limitProductionButtonText = "Limit Production"; if (product.prdman[city][0]) { limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")"; } - const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product); + const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city); // Discontinue Button const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product); @@ -56,8 +68,8 @@ function ProductComponent(props) { if (!product.fin) { if (hasUpgradeDashboard) { return ( -
-

Designing {product.name}...

+
+

Designing {product.name}...


{numeralWrapper.format(product.prog, "0.00")}% complete


@@ -76,8 +88,8 @@ function ProductComponent(props) { ) } else { return ( -
-

Designing {product.name}...

+
+

Designing {product.name}...


{numeralWrapper.format(product.prog, "0.00")}% complete

); @@ -87,13 +99,13 @@ function ProductComponent(props) { return (

- {product.name}: {numeralWrapper.format(product.data[city][0], nf)} ({numeralWrapper.format(totalGain, nf)}/s) + {product.name}: {numeralWrapper.format(product.data[city][0], nfB)} ({numeralWrapper.format(totalGain, nfB)}/s) - Prod: {numeralWrapper.format(product.data[city][1], nf)}/s + Prod: {numeralWrapper.format(product.data[city][1], nfB)}/s
- Sell: {numeralWrapper.format(product.data[city][2], nf)} /s + Sell: {numeralWrapper.format(product.data[city][2], nfB)} /s
-

+


Rating: {numeralWrapper.format(product.rat, nf)} @@ -119,13 +131,13 @@ function ProductComponent(props) { } -

+


Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)} An estimate of the material cost it takes to create this Product. -

+


Est. Market Price: {numeralWrapper.formatMoney(product.pCost + product.rat / product.mku)} @@ -161,6 +173,7 @@ function MaterialComponent(props) { // Numeraljs formatter const nf = "0.000"; + const nfB = "0.000a"; // For numbers that might be biger // Total gain or loss of this material (per second) const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp; @@ -190,8 +203,12 @@ function MaterialComponent(props) { // Sell material button let sellButtonText; if (mat.sllman[0]) { - sellButtonText = (mat.sllman[1] === -1 ? "Sell (" + numeralWrapper.format(mat.sll, nf) + "/MAX)" : - "Sell (" + numeralWrapper.format(mat.sll, nf) + "/" + numeralWrapper.format(mat.sllman[1], nf) + ")"); + if (isString(mat.sllman[1])) { + sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nf)}/${mat.sllman[1]})` + } else { + sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nf)}/${numeralWrapper.format(mat.sllman[1], nf)})`; + } + if (mat.sCost) { if (mat.marketTa1) { sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit); @@ -214,13 +231,13 @@ function MaterialComponent(props) {

- {mat.name}: {numeralWrapper.format(mat.qty, nf)} ({numeralWrapper.format(totalGain, nf)}/s) + {mat.name}: {numeralWrapper.format(mat.qty, nfB)} ({numeralWrapper.format(totalGain, nfB)}/s) - Buy: {numeralWrapper.format(mat.buy, nf)}
- Prod: {numeralWrapper.format(mat.prd, nf)}
- Sell: {numeralWrapper.format(mat.sll, nf)}
- Export: {numeralWrapper.format(mat.totalExp, nf)}
- Import: {numeralWrapper.format(mat.imp, nf)} + Buy: {numeralWrapper.format(mat.buy, nfB)}
+ Prod: {numeralWrapper.format(mat.prd, nfB)}
+ Sell: {numeralWrapper.format(mat.sll, nfB)}
+ Export: {numeralWrapper.format(mat.totalExp, nfB)}
+ Import: {numeralWrapper.format(mat.imp, nfB)} { corp.unlockUpgrades[2] === 1 &&
} @@ -244,7 +261,7 @@ function MaterialComponent(props) {


- Quality: {numeralWrapper.format(mat.qlt, "0.00")} + Quality: {numeralWrapper.format(mat.qlt, "0.00a")} The quality of your material. Higher quality will lead to more sales @@ -370,7 +387,8 @@ export class IndustryWarehouse extends BaseReactComponent { // Smart Supply Checkbox const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox"; const smartSupplyOnChange = (e) => { - warehouse.smartSupplyEnabled = e.target.value; + warehouse.smartSupplyEnabled = e.target.checked; + corp.rerender(); } // Materials that affect Production multiplier @@ -409,14 +427,15 @@ export class IndustryWarehouse extends BaseReactComponent { if (division.makesProducts && Object.keys(division.products).length > 0) { for (const productName in division.products) { if (division.products[productName] instanceof Product) { - products.push({ + products.push(ProductComponent({ + city: this.props.currentCity, corp: corp, division: division, eventHandler: this.eventHandler(), key: productName, product: division.products[productName], warehouse: warehouse, - }) + })); } } } @@ -425,9 +444,7 @@ export class IndustryWarehouse extends BaseReactComponent {

Storage: {numeralWrapper.format(warehouse.sizeUsed, "0.000")} / {numeralWrapper.format(warehouse.size, "0.000")} - - {warehouse.breakdown} - +

} @@ -481,9 +498,11 @@ export class IndustryWarehouse extends BaseReactComponent { return this.renderWarehouseUI(); } else { return ( - +
+ +
) } } diff --git a/src/Corporation/ui/MainPanel.jsx b/src/Corporation/ui/MainPanel.jsx index f67827feb..03080e3b5 100644 --- a/src/Corporation/ui/MainPanel.jsx +++ b/src/Corporation/ui/MainPanel.jsx @@ -23,6 +23,15 @@ export class MainPanel extends BaseReactComponent { } } + // We can pass this setter to child components + changeCityState(newCity) { + if (Object.values(Cities).includes(newCity)) { + this.state.city = newCity; + } else { + console.error(`Tried to change MainPanel's city state to an invalid city: ${newCity}`); + } + } + // Determines what UI content to render based on routing renderContent() { if (this.routing().isOnOverviewPage()) { @@ -68,11 +77,13 @@ export class MainPanel extends BaseReactComponent { } } } + const cityTabs = ( ) diff --git a/src/Corporation/ui/Overview.jsx b/src/Corporation/ui/Overview.jsx index 6ae424863..aea620ec0 100644 --- a/src/Corporation/ui/Overview.jsx +++ b/src/Corporation/ui/Overview.jsx @@ -64,7 +64,7 @@ export class Overview extends BaseReactComponent { dividendStr + "Publicly Traded: " + (this.corp().public ? "Yes" : "No") + "
" + "Owned Stock Shares: " + numeralWrapper.format(this.corp().numShares, '0.000a') + "
" + - "Stock Price: " + (this.corp().public ? "$" + numeralWrapper.formatMoney(this.corp().sharePrice) : "N/A") + "
" + + "Stock Price: " + (this.corp().public ? numeralWrapper.formatMoney(this.corp().sharePrice) : "N/A") + "
" + "

Total Stock Shares: " + numeralWrapper.format(this.corp().totalShares, "0.000a") + "" + `Outstanding Shares: ${numeralWrapper.format(this.corp().issuedShares, "0.000a")}
` + diff --git a/src/Script/ScriptHelpers.js b/src/Script/ScriptHelpers.js index 821dce013..3eff77f47 100644 --- a/src/Script/ScriptHelpers.js +++ b/src/Script/ScriptHelpers.js @@ -1,3 +1,5 @@ +import { Script } from "./Script"; + import { calculateRamUsage } from "./RamCalculations"; import { isScriptFilename } from "./ScriptHelpersTS"; @@ -269,7 +271,7 @@ function saveAndCloseScriptEditor() { } //If the current script does NOT exist, create a new one - var script = new Script(); + const script = new Script(); script.saveScript(getCurrentEditor().getCode(), Player); s.scripts.push(script); } else if (filename.endsWith(".txt")) { diff --git a/src/utils/calculateEffectWithFactors.ts b/src/utils/calculateEffectWithFactors.ts new file mode 100644 index 000000000..fc394ff3c --- /dev/null +++ b/src/utils/calculateEffectWithFactors.ts @@ -0,0 +1,45 @@ +/** + * This is a component that implements a mathematical formula used commonly throughout the + * game. This formula is (typically) used to calculate the effect that various statistics + * have on a game mechanic. It looks something like: + * + * (stat ^ exponential factor) + (stat / linear factor) + * + * where the exponential factor is a number between 0 and 1 and the linear factor + * is typically a relatively larger number. + * + * This formula ensures that the effects of the statistic that is being processed + * has diminishing returns, but never loses its effectiveness as you continue + * to raise it. + * + * There are two implementations of this component. One is simply a function that + * can be called with the stat and the exponential/linear factors. The other is a + * class where the exponential and linear factors are defined upon construction. + */ +export function calculateEffectWithFactors(n: number, expFac: number, linearFac: number): number { + if (expFac <= 0 || expFac >= 1) { + console.warn(`Exponential factor is ${expFac}. This is not an intended value for it`); + } + if (linearFac < 1) { + console.warn(`Linear factor is ${linearFac}. This is not an intended value for it`); + } + + return (Math.pow(n, expFac)) + (n / linearFac); +} + +export class EffectWithFactors { + // Exponential factor + private expFac: number; + + // Linear Factor + private linearFac: number; + + constructor(expFac: number, linearFac: number) { + this.expFac = expFac; + this.linearFac = linearFac; + } + + calculate(n: number): number { + return calculateEffectWithFactors(n, this.expFac, this.linearFac); + } +} diff --git a/utils/helpers/exceptionAlert.ts b/utils/helpers/exceptionAlert.ts index c5bc571e3..2a6998c89 100644 --- a/utils/helpers/exceptionAlert.ts +++ b/utils/helpers/exceptionAlert.ts @@ -6,6 +6,7 @@ interface IError { } export function exceptionAlert(e: IError): void { + console.error(e); dialogBoxCreate("Caught an exception: " + e + "

" + "Filename: " + (e.fileName || "UNKNOWN FILE NAME") + "

" + "Line Number: " + (e.lineNumber || "UNKNOWN LINE NUMBER") + "

" + From e6c5ff7ab7f98a130236eb56f9394893a69b25f6 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sun, 17 Mar 2019 17:58:06 -0700 Subject: [PATCH 18/34] Fixed more bugs with new Corporation UI. Minor rebalancing on Corp UI. Changed the Market TA researches to allow you to automatically set price --- css/companymanagement.scss | 3 +- src/Constants.ts | 2 + src/Corporation/Corporation.jsx | 155 ++++++---- src/Corporation/Material.ts | 1 + src/Corporation/MaterialSizes.ts | 24 +- src/Corporation/Product.ts | 33 +- src/Corporation/Warehouse.ts | 4 + src/Corporation/data/ResearchMetadata.ts | 6 +- .../ui/CorporationUIEventHandler.js | 281 +++++++++++++++--- src/Corporation/ui/IndustryOffice.jsx | 15 +- src/Corporation/ui/IndustryWarehouse.jsx | 56 +++- src/Locations/createCityMap.ts | 18 ++ .../Sleeve/SleeveAugmentationsUI.ts | 6 +- tsconfig.json | 2 +- 14 files changed, 460 insertions(+), 146 deletions(-) create mode 100644 src/Locations/createCityMap.ts diff --git a/css/companymanagement.scss b/css/companymanagement.scss index 7a8944a2b..e93dd331b 100644 --- a/css/companymanagement.scss +++ b/css/companymanagement.scss @@ -10,7 +10,8 @@ #cmpy-mgmt-container p, #cmpy-mgmt-container a, -#cmpy-mgmt-container div { +#cmpy-mgmt-container div, +#cmpy-mgmt-container br { font-size: $defaultFontSize * 0.8125; } diff --git a/src/Constants.ts b/src/Constants.ts index 33022df26..1d48c94a2 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -297,9 +297,11 @@ export let CONSTANTS: IMap = { ** Significantly changed the effects of the different employee positions. See updated descriptions ** Reduced the amount of money you gain from private investors ** Training employees is now 3x more effective + ** Bug Fix: An industry's products are now properly separated between different cities * Rebalanced BitNode-3 to make it slightly harder * Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina + * Bug Fix: The cost of purchasing Augmentations for Duplicate Sleeves no longer scales with how many Augs you've purchased for yourself ` } diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx index d3e66643d..a8879b351 100644 --- a/src/Corporation/Corporation.jsx +++ b/src/Corporation/Corporation.jsx @@ -19,6 +19,7 @@ import { CONSTANTS } from "../Constants"; import { Factions } from "../Faction/Factions"; import { showLiterature } from "../Literature"; import { Locations } from "../Locations"; +import { createCityMap } from "../Locations/Cities"; import { Player } from "../Player"; import { numeralWrapper } from "../ui/numeralFormat"; @@ -590,7 +591,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { //At the start of the export state, set the imports of everything to 0 if (this.state === "EXPORT") { - for (var i = 0; i < Cities.length; ++i) { + for (let i = 0; i < Cities.length; ++i) { var city = Cities[i], office = this.offices[city]; if (!(this.warehouses[city] instanceof Warehouse)) { continue; @@ -605,7 +606,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { } } - for (var i = 0; i < Cities.length; ++i) { + for (let i = 0; i < Cities.length; ++i) { var city = Cities[i], office = this.offices[city]; if (this.warehouses[city] instanceof Warehouse) { @@ -665,19 +666,17 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { prod = maxProd; } prod *= (SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle - //Calculate net change in warehouse storage making - //the produced materials will cost + + // Calculate net change in warehouse storage making the produced materials will cost var totalMatSize = 0; - for (var tmp = 0; tmp < this.prodMats.length; ++tmp) { + for (let tmp = 0; tmp < this.prodMats.length; ++tmp) { totalMatSize += (MaterialSizes[this.prodMats[tmp]]); } - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - var normQty = this.reqMats[reqMatName]; - totalMatSize -= (MaterialSizes[reqMatName] * normQty); - } + for (const reqMatName in this.reqMats) { + var normQty = this.reqMats[reqMatName]; + totalMatSize -= (MaterialSizes[reqMatName] * normQty); } - //If not enough space in warehouse, limit the amount of produced materials + // If not enough space in warehouse, limit the amount of produced materials if (totalMatSize > 0) { var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize); prod = Math.min(maxAmt, prod); @@ -685,10 +684,10 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { if (prod < 0) {prod = 0;} - //Keep track of production for smart supply (/s) + // Keep track of production for smart supply (/s) warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles)); - //Make sure we have enough resource to make our materials + // Make sure we have enough resource to make our materials var producableFrac = 1; for (var reqMatName in this.reqMats) { if (this.reqMats.hasOwnProperty(reqMatName)) { @@ -700,17 +699,15 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { } if (producableFrac <= 0) {producableFrac = 0; prod = 0;} - //Make our materials if they are producable + // Make our materials if they are producable if (producableFrac > 0 && prod > 0) { - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac); - warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; - warehouse.materials[reqMatName].prd = 0; - warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles); - } + for (const reqMatName in this.reqMats) { + var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac); + warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; + warehouse.materials[reqMatName].prd = 0; + warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles); } - for (var j = 0; j < this.prodMats.length; ++j) { + for (let j = 0; j < this.prodMats.length; ++j) { warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac); warehouse.materials[this.prodMats[j]].qlt = (office.employeeProd[EmployeePositions.Engineer] / 90 + @@ -718,7 +715,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3); } } else { - for (var reqMatName in this.reqMats) { + for (const reqMatName in this.reqMats) { if (this.reqMats.hasOwnProperty(reqMatName)) { warehouse.materials[reqMatName].prd = 0; } @@ -726,18 +723,16 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { } //Per second - var fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles); - for (var fooI = 0; fooI < this.prodMats.length; ++fooI) { + const fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles); + for (let fooI = 0; fooI < this.prodMats.length; ++fooI) { warehouse.materials[this.prodMats[fooI]].prd = fooProd; } } else { //If this doesn't produce any materials, then it only creates //Products. Creating products will consume materials. The //Production of all consumed materials must be set to 0 - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - warehouse.materials[reqMatName].prd = 0; - } + for (const reqMatName in this.reqMats) { + warehouse.materials[reqMatName].prd = 0; } } break; @@ -751,12 +746,39 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { mat.sll = 0; continue; } - var mat = warehouse.materials[matName]; - // Calculate sale cost + // Sale multipliers + const businessFactor = this.getBusinessFactor(office); //Business employee productivity + const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity + const marketFactor = this.getMarketFactor(mat); //Competition + demand + + // Determine the cost that the material will be sold at const markupLimit = mat.getMarkupLimit(); var sCost; - if (mat.marketTa1) { + if (mat.marketTa2) { + const prod = mat.prd; + + // Reverse engineer the 'maxSell' formula + // 1. Set 'maxSell' = prod + // 2. Substitute formula for 'markup' + // 3. Solve for 'sCost' + const numerator = markupLimit; + const sqrtNumerator = prod; + const sqrtDenominator = ((mat.qlt + .001) + * marketFactor + * businessFactor + * company.getSalesMultiplier() + * advertisingFactor + * this.getSalesMultiplier()); + const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); + const optimalPrice = (numerator / denominator) + mat.bCost; + + // We'll store this "Optimal Price" in a property so that we don't have + // to re-calculate it for the UI + mat.marketTa2Price = optimalPrice; + + sCost = optimalPrice; + } else if (mat.marketTa1) { sCost = mat.bCost + markupLimit; } else if (isString(mat.sCost)) { sCost = mat.sCost.replace(/MP/g, mat.bCost); @@ -780,9 +802,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { markup = mat.bCost / sCost; } } - var businessFactor = this.getBusinessFactor(office); //Business employee productivity - var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity - var marketFactor = this.getMarketFactor(mat); //Competition + demand + var maxSell = (mat.qlt + .001) * marketFactor * markup @@ -905,8 +925,8 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { //Produce Scientific Research based on R&D employees //Scientific Research can be produced without a warehouse if (office instanceof OfficeSpace) { - this.sciResearch.qty += (.005 - * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.55) + this.sciResearch.qty += (.004 + * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5) * company.getScientificResearchMultiplier() * this.getScientificResearchMultiplier()); } @@ -962,9 +982,9 @@ Industry.prototype.processProducts = function(marketCycles=1, corporation) { //Processes FINISHED products Industry.prototype.processProduct = function(marketCycles=1, product, corporation) { - var totalProfit = 0; - for (var i = 0; i < Cities.length; ++i) { - var city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city]; + let totalProfit = 0; + for (let i = 0; i < Cities.length; ++i) { + let city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city]; if (warehouse instanceof Warehouse) { switch(this.state) { @@ -1040,27 +1060,60 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio } } - //Since its a product, its production cost is increased for labor + // Since its a product, its production cost is increased for labor product.pCost *= ProductProductionCostRatio; - //Calculate Sale Cost (sCost), which could be dynamically evaluated + // Sale multipliers + const businessFactor = this.getBusinessFactor(office); //Business employee productivity + const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity + const marketFactor = this.getMarketFactor(product); //Competition + demand + + // Calculate Sale Cost (sCost), which could be dynamically evaluated + const markupLimit = product.rat / product.mku; var sCost; - if (isString(product.sCost)) { + if (product.marketTa2) { + const prod = product.data[city][1]; + + // Reverse engineer the 'maxSell' formula + // 1. Set 'maxSell' = prod + // 2. Substitute formula for 'markup' + // 3. Solve for 'sCost'roduct.pCost = sCost + const numerator = markupLimit; + const sqrtNumerator = prod; + const sqrtDenominator = (0.5 + * Math.pow(product.rat, 0.65) + * marketFactor + * corporation.getSalesMultiplier() + * businessFactor + * advertisingFactor + * this.getSalesMultiplier()); + const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); + let optimalPrice; + if (sqrtDenominator === 0 || denominator === 0) { + optimalPrice = 0; + } else { + optimalPrice = (numerator / denominator) + product.pCost; + } + + // Store this "optimal Price" in a property so we don't have to re-calculate for UI + product.marketTa2Price[city] = optimalPrice; + sCost = optimalPrice; + } else if (product.marketTa1) { + sCost = product.pCost + markupLimit; + } else if (isString(product.sCost)) { sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku); sCost = eval(sCost); } else { sCost = product.sCost; } - var markup = 1, markupLimit = product.rat / product.mku; + var markup = 1; if (sCost > product.pCost) { if ((sCost - product.pCost) > markupLimit) { markup = markupLimit / (sCost - product.pCost); } } - var businessFactor = this.getBusinessFactor(office); //Business employee productivity - var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity - var marketFactor = this.getMarketFactor(product); //Competition + demand + var maxSell = 0.5 * Math.pow(product.rat, 0.65) * marketFactor @@ -1085,8 +1138,9 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio } else if (product.sllman[city][0] && product.sllman[city][1] > 0) { //Sell amount is manually limited sellAmt = Math.min(maxSell, product.sllman[city][1]); + } else if (product.sllman[city][0] === false){ + sellAmt = 0; } else { - //Backwards compatibility, -1 = 0 sellAmt = maxSell; } if (sellAmt < 0) { sellAmt = 0; } @@ -1114,8 +1168,7 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio return totalProfit; } -Industry.prototype.discontinueProduct = function(product, parentRefs) { - var company = parentRefs.company, industry = parentRefs.industry; +Industry.prototype.discontinueProduct = function(product) { for (var productName in this.products) { if (this.products.hasOwnProperty(productName)) { if (product === this.products[productName]) { @@ -1182,7 +1235,7 @@ Industry.prototype.getOfficeProductivity = function(office, params) { // Returns a multiplier based on the office' 'Business' employees that affects sales Industry.prototype.getBusinessFactor = function(office) { const businessProd = 1 + office.employeeProd[EmployeePositions.Business]; - + return calculateEffectWithFactors(businessProd, 0.26, 10e3); } diff --git a/src/Corporation/Material.ts b/src/Corporation/Material.ts index 30c82c139..7ad8f4ae6 100644 --- a/src/Corporation/Material.ts +++ b/src/Corporation/Material.ts @@ -66,6 +66,7 @@ export class Material { // Flags that signal whether automatic sale pricing through Market TA is enabled marketTa1: boolean = false; marketTa2: boolean = false; + marketTa2Price: number = 0; constructor(params: IConstructorParams = {}) { if (params.name) { this.name = params.name; } diff --git a/src/Corporation/MaterialSizes.ts b/src/Corporation/MaterialSizes.ts index 6f744cc12..2ec694f25 100644 --- a/src/Corporation/MaterialSizes.ts +++ b/src/Corporation/MaterialSizes.ts @@ -2,15 +2,17 @@ import { IMap } from "../types"; // Map of material (by name) to their sizes (how much space it takes in warehouse) export const MaterialSizes: IMap = { - Water: 0.05, - Energy: 0.01, - Food: 0.03, - Plants: 0.05, - Metal: 0.1, - Hardware: 0.06, - Chemicals: 0.05, - Drugs: 0.02, - Robots: 0.5, - AICores: 0.1, - RealEstate: 0, + Water: 0.05, + Energy: 0.01, + Food: 0.03, + Plants: 0.05, + Metal: 0.1, + Hardware: 0.06, + Chemicals: 0.05, + Drugs: 0.02, + Robots: 0.5, + AICores: 0.1, + RealEstate: 0, + "Real Estate": 0, + "AI Cores": 0, } diff --git a/src/Corporation/Product.ts b/src/Corporation/Product.ts index 2a7d6afb1..befcc7396 100644 --- a/src/Corporation/Product.ts +++ b/src/Corporation/Product.ts @@ -4,8 +4,10 @@ import { ProductRatingWeights, IProductRatingWeight } from "./ProductRatingWeights"; import { Cities } from "../Locations/Cities"; +import { createCityMap } from "../Locations/createCityMap"; import { IMap } from "../types"; + import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; @@ -89,14 +91,7 @@ export class Product { // Data refers to the production, sale, and quantity of the products // These values are specific to a city // For each city, the data is [qty, prod, sell] - data: IMap = { - [Cities.Aevum]: [0, 0, 0], - [Cities.Chongqing]: [0, 0, 0], - [Cities.Sector12]: [0, 0, 0], - [Cities.NewTokyo]: [0, 0, 0], - [Cities.Ishima]: [0, 0, 0], - [Cities.Volhaven]: [0, 0, 0], - } + data: IMap = createCityMap([0, 0, 0]); // Location of this Product // Only applies for location-based products like restaurants/hospitals @@ -113,23 +108,13 @@ export class Product { // Data to keep track of whether production/sale of this Product is // manually limited. These values are specific to a city // [Whether production/sale is limited, limit amount] - prdman: IMap = { - [Cities.Aevum]: [false, 0], - [Cities.Chongqing]: [false, 0], - [Cities.Sector12]: [false, 0], - [Cities.NewTokyo]: [false, 0], - [Cities.Ishima]: [false, 0], - [Cities.Volhaven]: [false, 0], - } + prdman: IMap = createCityMap([false, 0]); + sllman: IMap = createCityMap([false, 0]); - sllman: IMap = { - [Cities.Aevum]: [false, 0], - [Cities.Chongqing]: [false, 0], - [Cities.Sector12]: [false, 0], - [Cities.NewTokyo]: [false, 0], - [Cities.Ishima]: [false, 0], - [Cities.Volhaven]: [false, 0], - } + // Flags that signal whether automatic sale pricing through Market TA is enabled + marketTa1: boolean = false; + marketTa2: boolean = false; + marketTa2Price: IMap = createCityMap(0); constructor(params: IConstructorParams={}) { this.name = params.name ? params.name : ""; diff --git a/src/Corporation/Warehouse.ts b/src/Corporation/Warehouse.ts index ecbaa826a..15bdf03b6 100644 --- a/src/Corporation/Warehouse.ts +++ b/src/Corporation/Warehouse.ts @@ -46,6 +46,10 @@ export class Warehouse { // Whether Smart Supply is enabled for this Industry (the Industry that this Warehouse is for) smartSupplyEnabled: boolean = false; + // Flag that indicates whether Smart Supply accounts for imports when calculating + // the amount fo purchase + smartSupplyConsiderExports: boolean = false; + // Stores the amount of product to be produced. Used for Smart Supply unlock. // The production tracked by smart supply is always based on the previous cycle, // so it will always trail the "true" production by 1 cycle diff --git a/src/Corporation/data/ResearchMetadata.ts b/src/Corporation/data/ResearchMetadata.ts index 57f6f7d5e..73e6ec5d5 100644 --- a/src/Corporation/data/ResearchMetadata.ts +++ b/src/Corporation/data/ResearchMetadata.ts @@ -94,11 +94,13 @@ export const researchMetadata: IConstructorParams[] = [ }, { name: "Market-TA.II", - cost: 40e3, + cost: 50e3, desc: "Develop double-advanced AI software that uses technical analysis to " + "help you understand and exploit the market. This research " + "allows you to know how many sales of a Material/Product you lose or gain " + - "from having too high or too low or a sale price.", + "from having too high or too low or a sale price. It also lets you automatically " + + "set the sale price of your Materials/Products at the optimal price such that " + + "the amount sold matches the amount produced.", }, { name: "Overclock", diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js index 6feec0808..a43d78819 100644 --- a/src/Corporation/ui/CorporationUIEventHandler.js +++ b/src/Corporation/ui/CorporationUIEventHandler.js @@ -17,10 +17,14 @@ import { Industries, IndustryDescriptions, IndustryResearchTrees } from "../IndustryData"; +import { MaterialSizes } from "../MaterialSizes"; + import { Product } from "../Product"; import { Player } from "../../Player"; +import { Cities } from "../../Locations/Cities"; + import { numeralWrapper } from "../../ui/numeralFormat"; import { dialogBoxCreate } from "../../../utils/DialogBox"; @@ -81,7 +85,7 @@ export class CorporationEventHandler { var totalAmount = Number(money) + (stockShares * stockPrice); var repGain = totalAmount / BribeToRepRatio; - repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") + + repGainText.innerText = "You will gain " + numeralWrapper.format(repGain, "0,0") + " reputation with " + factionSelector.options[factionSelector.selectedIndex].value + " with this bribe"; @@ -104,7 +108,7 @@ export class CorporationEventHandler { var totalAmount = money + (stockShares * stockPrice); var repGain = totalAmount / BribeToRepRatio; console.log("repGain: " + repGain); - repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") + + repGainText.innerText = "You will gain " + numeralWrapper.format(repGain, "0,0") + " reputation with " + factionSelector.options[factionSelector.selectedIndex].value + " with this bribe"; @@ -131,7 +135,7 @@ export class CorporationEventHandler { } else { var totalAmount = money + (stockShares * stockPrice); var repGain = totalAmount / BribeToRepRatio; - dialogBoxCreate("You gained " + formatNumber(repGain, 0) + + dialogBoxCreate("You gained " + numeralWrapper.format(repGain, "0,0") + " reputation with " + fac.name + " by bribing them."); fac.playerReputation += repGain; this.corp.funds = this.corp.funds.minus(money); @@ -170,7 +174,6 @@ export class CorporationEventHandler { type:"number", placeholder:"Shares to buyback", margin:"5px", inputListener: ()=> { var numShares = Math.round(input.value); - //TODO add conditional for if player doesn't have enough money if (isNaN(numShares) || numShares <= 0) { costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback" } else if (numShares > this.corp.issuedShares) { @@ -228,7 +231,7 @@ export class CorporationEventHandler { } // Create a popup that lets the player discontinue a product - createDiscontinueProductPopup(product) { + createDiscontinueProductPopup(product, industry) { const popupId = "cmpy-mgmt-discontinue-product-popup"; const txt = createElement("p", { innerText:"Are you sure you want to do this? Discontinuing a product " + @@ -237,9 +240,9 @@ export class CorporationEventHandler { "removed and left unsold", }); const confirmBtn = createElement("button", { - class:"a-link-button",innerText:"Discontinue", + class:"popup-box-button",innerText:"Discontinue", clickListener: () => { - industry.discontinueProduct(product, parentRefs); + industry.discontinueProduct(product); removeElementById(popupId); this.rerender(); return false; @@ -247,7 +250,7 @@ export class CorporationEventHandler { }); const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); - createPopup(popupId, [txt, confirmBtn, cancelBtn]); + createPopup(popupId, [txt, cancelBtn, confirmBtn]); } // Create a popup that lets the player manage exports @@ -669,8 +672,8 @@ export class CorporationEventHandler { productNameInput.focus(); } - // Create a popup that lets the player use the Market TA research - createMarketTaPopup(mat, industry) { + // Create a popup that lets the player use the Market TA research for Materials + createMaterialMarketTaPopup(mat, industry) { const corp = this.corp; const popupId = "cmpy-mgmt-marketta-popup"; @@ -694,19 +697,21 @@ export class CorporationEventHandler { "be sold at the price identified by Market-TA.I (i.e. the price shown above)" }) const useTa1AutoSaleCheckbox = createElement("input", { + checked: mat.marketTa1, id: useTa1AutoSaleId, + margin: "3px", type: "checkbox", - value: mat.marketTa1, changeListener: (e) => { - mat.marketTa1 = e.target.value; + mat.marketTa1 = e.target.checked; } }); - useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox); + useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel); useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox); const closeBtn = createPopupCloseButton(popupId, { class: "std-button", display: "block", + innerText: "Close", }); if (industry.hasResearch("Market-TA.II")) { @@ -741,11 +746,36 @@ export class CorporationEventHandler { } ta2Text.innerHTML = `
Market-TA.II
` + `If you sell at ${numeralWrapper.formatMoney(sCost)}, ` + - `then you will sell ${formatNumber(markup, 5)}x as much compared ` + + `then you will sell ${numeralWrapper.format(markup, "0.00000")}x as much compared ` + `to if you sold at market price.`; } updateTa2Text(); - createPopup(popupId, [ta1, ta2Text, ta2Input, closeBtn]); + + // Enable using Market-TA2 for automatically setting sale price + const useTa2AutoSaleId = "cmpy-mgmt-marketa2-checkbox"; + const useTa2AutoSaleDiv = createElement("div", { display: "block" }); + const useTa2AutoSaleLabel = createElement("label", { + color: "white", + for: useTa2AutoSaleId, + innerText: "Use Market-TA.II for Auto-Sale Price", + tooltip: "If this is enabled, then this Material will automatically " + + "be sold at the optimal price such that the amount sold matches the " + + "amount produced. (i.e. the highest possible price, while still ensuring " + + " that all produced materials will be sold)" + }) + const useTa2AutoSaleCheckbox = createElement("input", { + checked: mat.marketTa2, + id: useTa2AutoSaleId, + margin: "3px", + type: "checkbox", + changeListener: (e) => { + mat.marketTa2 = e.target.checked; + } + }); + useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel); + useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox); + + createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, closeBtn]); } else { // Market-TA.I only createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]); @@ -775,6 +805,7 @@ export class CorporationEventHandler { display:"inline-block", innerText: "Confirm", clickListener: () => { + if (citySelector.length <= 0) { return false; } let city = citySelector.options[citySelector.selectedIndex].value; if (this.corp.funds.lt(OfficeInitialCost)) { dialogBoxCreate("You don't have enough company funds to open a new office!"); @@ -921,8 +952,110 @@ export class CorporationEventHandler { return false; } + // Create a popup that lets the player use the Market TA research for Products + createProductMarketTaPopup(product, industry) { + const corp = this.corp; + + const popupId = "cmpy-mgmt-marketta-popup"; + const markupLimit = product.rat / product.mku; + const ta1 = createElement("p", { + innerHTML: "Market-TA.I
" + + "The maximum sale price you can mark this up to is " + + numeralWrapper.formatMoney(product.pCost + markupLimit) + + ". This means that if you set the sale price higher than this, " + + "you will begin to experience a loss in number of sales", + }); + + // Enable using Market-TA1 for automatically setting sale price + const useTa1AutoSaleId = "cmpy-mgmt-marketa1-checkbox"; + const useTa1AutoSaleDiv = createElement("div", { display: "block" }); + const useTa1AutoSaleLabel = createElement("label", { + color: "white", + for: useTa1AutoSaleId, + innerText: "Use Market-TA.I for Auto-Sale Price", + tooltip: "If this is enabled, then this Product will automatically " + + "be sold at the price identified by Market-TA.I (i.e. the price shown above)" + }) + const useTa1AutoSaleCheckbox = createElement("input", { + checked: product.marketTa1, + id: useTa1AutoSaleId, + margin: "3px", + type: "checkbox", + changeListener: (e) => { + product.marketTa1 = e.target.checked; + } + }); + useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel); + useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox); + + const closeBtn = createPopupCloseButton(popupId, { + class: "std-button", + display: "block", + innerText: "Close", + }); + + if (industry.hasResearch("Market-TA.II")) { + let updateTa2Text; + const ta2Text = createElement("p"); + const ta2Input = createElement("input", { + marginTop: "4px", + onkeyup: (e) => { + e.preventDefault(); + updateTa2Text(); + }, + type: "number", + value: product.pCost, + }); + + // Function that updates the text in ta2Text element + updateTa2Text = function() { + const sCost = parseFloat(ta2Input.value); + let markup = 1; + if (sCost > product.pCost) { + if ((sCost - product.pCost) > markupLimit) { + markup = markupLimit / (sCost - product.pCost); + } + } + ta2Text.innerHTML = `
Market-TA.II
` + + `If you sell at ${numeralWrapper.formatMoney(sCost)}, ` + + `then you will sell ${numeralWrapper.format(markup, "0.00000")}x as much compared ` + + `to if you sold at market price.`; + } + updateTa2Text(); + + // Enable using Market-TA2 for automatically setting sale price + const useTa2AutoSaleId = "cmpy-mgmt-marketa2-checkbox"; + const useTa2AutoSaleDiv = createElement("div", { display: "block" }); + const useTa2AutoSaleLabel = createElement("label", { + color: "white", + for: useTa2AutoSaleId, + innerText: "Use Market-TA.II for Auto-Sale Price", + tooltip: "If this is enabled, then this Product will automatically " + + "be sold at the optimal price such that the amount sold matches the " + + "amount produced. (i.e. the highest possible price, while still ensuring " + + " that all produced materials will be sold)" + }) + const useTa2AutoSaleCheckbox = createElement("input", { + checked: product.marketTa2, + id: useTa2AutoSaleId, + margin: "3px", + type: "checkbox", + changeListener: (e) => { + product.marketTa2 = e.target.checked; + } + }); + useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel); + useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox); + + createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, closeBtn]); + } else { + // Market-TA.I only + createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]); + } + } + // Create a popup that lets the player purchase a Material - createPurchaseMaterialPopup(mat, industry) { + createPurchaseMaterialPopup(mat, industry, warehouse) { const corp = this.corp; const purchasePopupId = "cmpy-mgmt-material-purchase-popup"; @@ -980,15 +1113,20 @@ export class CorporationEventHandler { let bulkPurchaseCostTxt = createElement("p"); function updateBulkPurchaseText(amount) { - const cost = parseFloat(amount) * mat.bCost; - if (isNaN(cost)) { - dialogBoxCreate(`Bulk Purchase Cost calculated to be NaN. This is either due to ` + - `invalid input, or it is a bug (in which case you should report to dev)`); - return; - } + const parsedAmt = parseFloat(amount); + const cost = parsedAmt * mat.bCost; - bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(amt, "0,0.00")} of ` + - `${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`; + const matSize = MaterialSizes[mat.name]; + const maxAmount = ((warehouse.size - warehouse.sizeUsed) / matSize); + + if (parsedAmt * matSize > maxAmount) { + bulkPurchaseCostTxt.innerText = "Not enough warehouse space to purchase this amount"; + } else if (isNaN(cost)) { + bulkPurchaseCostTxt.innerText = "Invalid put for Bulk Purchase amount"; + } else { + bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(parsedAmt, "0,0.00")} of ` + + `${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`; + } } let bulkPurchaseConfirmBtn; @@ -998,7 +1136,7 @@ export class CorporationEventHandler { type: "number", onkeyup: (e) => { e.preventDefault(); - updateBulkPurchaseText(); + updateBulkPurchaseText(e.target.value); if (e.keyCode === KEY.ENTER) {bulkPurchaseConfirmBtn.click();} } }); @@ -1007,7 +1145,15 @@ export class CorporationEventHandler { class: "std-button", innerText: "Confirm Bulk Purchase", clickListener: () => { - const amount = parseFloat(input.value); + const amount = parseFloat(bulkPurchaseInput.value); + + const matSize = MaterialSizes[mat.name]; + const maxAmount = ((warehouse.size - warehouse.sizeUsed) / matSize); + if (amount * matSize > maxAmount) { + dialogBoxCreate(`You do not have enough warehouse size to fit this purchase`); + return false; + } + if (isNaN(amount)) { dialogBoxCreate("Invalid input amount"); } else { @@ -1065,9 +1211,18 @@ export class CorporationEventHandler { if (e.keyCode === KEY.ENTER) {confirmBtn.click();} } }); + + let inputButtonInitValue = mat.sCost ? mat.sCost : null; + if (mat.marketTa2) { + inputButtonInitValue += " (Market-TA.II)"; + } else if (mat.marketTa1) { + inputButtonInitValue += " (Market-TA.I)"; + } + const inputPx = createElement("input", { type: "text", marginTop: "4px", - value: mat.sCost ? mat.sCost : null, placeholder: "Sell price", + value: inputButtonInitValue, + placeholder: "Sell price", onkeyup: (e) => { e.preventDefault(); if (e.keyCode === KEY.ENTER) {confirmBtn.click();} @@ -1179,16 +1334,41 @@ export class CorporationEventHandler { if (e.keyCode === KEY.ENTER) {confirmBtn.click();} } }); + + let inputButtonInitValue = product.sCost ? product.sCost : null; + if (product.marketTa2) { + inputButtonInitValue += " (Market-TA.II)"; + } else if (product.marketTa1) { + inputButtonInitValue += " (Market-TA.I)"; + } + const inputPx = createElement("input", { margin: "5px 0px 5px 0px", placeholder: "Sell price", type: "text", - value: product.sCost ? product.sCost : null, + value: inputButtonInitValue, onkeyup: (e) => { e.preventDefault(); if (e.keyCode === KEY.ENTER) {confirmBtn.click();} } }); + const checkboxDiv = createElement("div", { + border: "1px solid white", + display: "inline-block", + }) + const checkboxLabel = createElement("label", { + for: popupId + "-checkbox", + innerText: "Use same 'Sell Amount' for all cities", + }); + const checkbox = createElement("input", { + checked: true, + id: popupId + "-checkbox", + margin: "2px", + type: "checkbox", + }); + checkboxDiv.appendChild(checkboxLabel); + checkboxDiv.appendChild(checkbox); + confirmBtn = createElement("button", { class: "std-button", innerText: "Confirm", @@ -1220,7 +1400,10 @@ export class CorporationEventHandler { product.sCost = cost; } - //Parse quantity + // Array of all cities. Used later + const cities = Object.values(Cities); + + // Parse quantity if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) { //Dynamically evaluated quantity. First test to make sure its valid var qty = inputQty.value.replace(/\s+/g, ''); @@ -1238,8 +1421,16 @@ export class CorporationEventHandler { dialogBoxCreate("Invalid value or expression for sell price field"); return false; } - product.sllman[city][0] = true; - product.sllman[city][1] = qty; //Use sanitized input + if (checkbox.checked) { + for (let i = 0; i < cities.length; ++i) { + const tempCity = cities[i]; + product.sllman[tempCity][0] = true; + product.sllman[tempCity][1] = qty; //Use sanitized input + } + } else { + product.sllman[city][0] = true; + product.sllman[city][1] = qty; //Use sanitized input + } } else if (isNaN(inputQty.value)) { dialogBoxCreate("Invalid value for sell quantity field! Must be numeric"); return false; @@ -1247,10 +1438,25 @@ export class CorporationEventHandler { var qty = parseFloat(inputQty.value); if (isNaN(qty)) {qty = 0;} if (qty === 0) { - product.sllman[city][0] = false; + if (checkbox.checked) { + for (let i = 0; i < cities.length; ++i) { + const tempCity = cities[i]; + product.sllman[tempCity][0] = false; + } + } else { + product.sllman[city][0] = false; + } } else { - product.sllman[city][0] = true; - product.sllman[city][1] = qty; + if (checkbox.checked) { + for (let i = 0; i < cities.length; ++i) { + const tempCity = cities[i]; + product.sllman[tempCity][0] = true; + product.sllman[tempCity][1] = qty; + } + } else { + product.sllman[city][0] = true; + product.sllman[city][1] = qty; + } } } @@ -1259,9 +1465,12 @@ export class CorporationEventHandler { return false; } }); - const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + const cancelBtn = createPopupCloseButton(popupId, { class: "std-button" }); - createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn]); + const linebreak1 = createElement("br"); + + createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn, linebreak1, + checkboxDiv]); inputQty.focus(); } diff --git a/src/Corporation/ui/IndustryOffice.jsx b/src/Corporation/ui/IndustryOffice.jsx index 91d234a80..54ce1dfa7 100644 --- a/src/Corporation/ui/IndustryOffice.jsx +++ b/src/Corporation/ui/IndustryOffice.jsx @@ -305,7 +305,7 @@ export class IndustryOffice extends BaseReactComponent {

Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}

{ vechain && -

+

Material Production: {numeralWrapper.format(division.getOfficeProductivity(office), "0.000")} The base amount of material this office can produce. Does not include @@ -314,9 +314,12 @@ export class IndustryOffice extends BaseReactComponent {

} + { + vechain &&
+ } { vechain && -

+

Product Production: {numeralWrapper.format(division.getOfficeProductivity(office, {forProduct:true}), "0.000")} The base amount of any given Product this office can produce. Does not include @@ -325,15 +328,21 @@ export class IndustryOffice extends BaseReactComponent {

} + { + vechain &&
+ } { vechain && -

+

Business Multiplier: x{numeralWrapper.format(division.getBusinessFactor(office), "0.000")} The effect this office's 'Business' employees has on boosting sales

} + { + vechain &&
+ }

{EmployeePositions.Operations} ({this.state.numOperations}) diff --git a/src/Corporation/ui/IndustryWarehouse.jsx b/src/Corporation/ui/IndustryWarehouse.jsx index e13bb3cf7..8b69638f1 100644 --- a/src/Corporation/ui/IndustryWarehouse.jsx +++ b/src/Corporation/ui/IndustryWarehouse.jsx @@ -3,13 +3,13 @@ import React from "react"; import { BaseReactComponent } from "./BaseReactComponent"; -import { Material } from "../Material"; -import { Product } from "../Product"; - -import { Warehouse, +import { OfficeSpace, WarehouseInitialCost, WarehouseUpgradeBaseCost, ProductProductionCostRatio } from "../Corporation"; +import { Material } from "../Material"; +import { Product } from "../Product"; +import { Warehouse } from "../Warehouse"; import { numeralWrapper } from "../../ui/numeralFormat"; @@ -45,7 +45,12 @@ function ProductComponent(props) { sellButtonText = "Sell (0.000/0.000)"; } - if (product.sCost) { + if (product.marketTa2) { + sellButtonText += (" @ " + numeralWrapper.formatMoney(product.marketTa2Price[city])); + } else if (product.marketTa1) { + const markupLimit = product.rat / product.mku; + sellButtonText += (" @ " + numeralWrapper.formatMoney(product.pCost + markupLimit)); + } else if (product.sCost) { if (isString(product.sCost)) { sellButtonText += (" @ " + product.sCost); } else { @@ -55,14 +60,17 @@ function ProductComponent(props) { const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product, city); // Limit Production button - const limitProductionButtonText = "Limit Production"; + let limitProductionButtonText = "Limit Production"; if (product.prdman[city][0]) { limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")"; } const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city); // Discontinue Button - const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product); + const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product, division); + + // Market TA button + const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division); // Unfinished Product if (!product.fin) { @@ -83,6 +91,12 @@ function ProductComponent(props) { + { + division.hasResearch("Market-TA.I") && + + }

) @@ -139,7 +153,7 @@ function ProductComponent(props) {


- Est. Market Price: {numeralWrapper.formatMoney(product.pCost + product.rat / product.mku)} + Est. Market Price: {numeralWrapper.formatMoney(product.pCost)} An estimate of how much consumers are willing to pay for this product. Setting the sale price above this may result in less sales. Setting the sale price below this may result @@ -157,6 +171,12 @@ function ProductComponent(props) { + { + division.hasResearch("Market-TA.I") && + + }

) @@ -167,9 +187,14 @@ function MaterialComponent(props) { const corp = props.corp; const division = props.division; const warehouse = props.warehouse; + const city = props.city; const mat = props.mat; const eventHandler = props.eventHandler; const markupLimit = mat.getMarkupLimit(); + const office = division.offices[city]; + if (!(office instanceof OfficeSpace)) { + throw new Error(`Could not get OfficeSpace object for this city (${city})`); + } // Numeraljs formatter const nf = "0.000"; @@ -195,7 +220,7 @@ function MaterialComponent(props) { // Purchase material button const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nf)})`; const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button"; - const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division); + const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse); // Export material button const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat); @@ -209,10 +234,12 @@ function MaterialComponent(props) { sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nf)}/${numeralWrapper.format(mat.sllman[1], nf)})`; } - if (mat.sCost) { - if (mat.marketTa1) { - sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit); - } else if (isString(mat.sCost)) { + if (mat.marketTa2) { + sellButtonText += " @ " + numeralWrapper.formatMoney(mat.marketTa2Price); + } else if (mat.marketTa1) { + sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit); + } else if (mat.sCost) { + if (isString(mat.sCost)) { var sCost = mat.sCost.replace(/MP/g, mat.bCost); sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost)); } else { @@ -225,7 +252,7 @@ function MaterialComponent(props) { const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat); // Market TA button - const marketTaButtonOnClick = eventHandler.createMarketTaPopup.bind(eventHandler, mat, division); + const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division); return (
@@ -411,6 +438,7 @@ export class IndustryWarehouse extends BaseReactComponent { // Only create UI for materials that are relevant for the industry if (isRelevantMaterial(matName)) { mats.push(MaterialComponent({ + city: this.props.currentCity, corp: corp, division: division, eventHandler: this.eventHandler(), diff --git a/src/Locations/createCityMap.ts b/src/Locations/createCityMap.ts new file mode 100644 index 000000000..15d49b928 --- /dev/null +++ b/src/Locations/createCityMap.ts @@ -0,0 +1,18 @@ +/** + * Utility function that creates a "city map", which is an object where + * each city is a key (property). + * + * This map uses the official name of the city, NOT its key in the 'Cities' object + */ +import { Cities } from "./Cities"; +import { IMap } from "../types"; + +export function createCityMap(initValue: T): IMap { + const map: IMap = {}; + const cities = Object.values(Cities); + for (let i = 0; i < cities.length; ++i) { + map[cities[i]] = initValue; + } + + return map; +} diff --git a/src/PersonObjects/Sleeve/SleeveAugmentationsUI.ts b/src/PersonObjects/Sleeve/SleeveAugmentationsUI.ts index bfc4fe43b..471d95695 100644 --- a/src/PersonObjects/Sleeve/SleeveAugmentationsUI.ts +++ b/src/PersonObjects/Sleeve/SleeveAugmentationsUI.ts @@ -105,13 +105,13 @@ export function createSleevePurchaseAugsPopup(sleeve: Sleeve, p: IPlayer) { innerHTML: [ `

${aug.name}


`, - `Cost: ${numeralWrapper.formatMoney(aug.baseCost)}

`, + `Cost: ${numeralWrapper.formatMoney(aug.startingCost)}

`, `${aug.info}` ].join(" "), padding: "2px", clickListener: () => { - if (p.canAfford(aug.baseCost)) { - p.loseMoney(aug.baseCost); + if (p.canAfford(aug.startingCost)) { + p.loseMoney(aug.startingCost); sleeve.installAugmentation(aug); dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false) removeElementById(popupId); diff --git a/tsconfig.json b/tsconfig.json index 831898523..3c82af0b1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "baseUrl" : ".", "jsx": "react", - "lib" : ["es2016", "dom"], + "lib" : ["es2016", "dom", "es2017.object"], "module": "commonjs", "target": "es6", "sourceMap": true, From 8df7f8de4b3b86c1afcbed68a5fa651ec155d081 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 03:40:48 -0400 Subject: [PATCH 19/34] basic sleeve api --- src/Constants.ts | 1 + src/NetscriptFunctions.js | 107 +++++++++++++++++++++++++++++++++++++- 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/Constants.ts b/src/Constants.ts index cb34aeb8a..0a42627a6 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -90,6 +90,7 @@ export let CONSTANTS: IMap = { ScriptGetHackTimeRamCost: 0.05, ScriptGetFavorToDonate: 0.10, ScriptCodingContractBaseRamCost:10, + ScriptSleeveBaseRamCost: 0, // TODO: let big boss figure out balance. ScriptSingularityFn1RamCost: 1, ScriptSingularityFn2RamCost: 2, diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 38e6e3a7a..0f45bf5dc 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -4807,7 +4807,112 @@ function NetscriptFunctions(workerScript) { } return contract.getMaxNumTries() - contract.tries; }, - } + }, // End coding contracts + sleeve : { + getNumSleeves: function() { + if (workerScript.checkingRam) { + return updateStaticRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost); + return Player.sleeves.length; + }, + shockRecovery : function(sleeveNumber=0) { + if (workerScript.checkingRam) { + return updateStaticRam("shockRecovery", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("shockRecovery", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.shockRecovery(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + return Player.sleeves[sleeveNumber].shockRecovery(Player); + }, + synchronize : function(sleeveNumber=0) { + if (workerScript.checkingRam) { + return updateStaticRam("synchronize", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("synchronize", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.synchronize(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + return Player.sleeves[sleeveNumber].synchronize(Player); + }, + commitCrime : function(sleeveNumber=0, crimeName="") { + if (workerScript.checkingRam) { + return updateStaticRam("commitCrime", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("commitCrime", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.commitCrime(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + return Player.sleeves[sleeveNumber].commitCrime(Player, crimeName); + }, + takeUniversityCourse : function(sleeveNumber=0, universityName="", className="") { + if (workerScript.checkingRam) { + return updateStaticRam("takeUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("takeUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.takeUniversityCourse(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + return Player.sleeves[sleeveNumber].takeUniversityCourse(Player, universityName, className); + }, + travel : function(sleeveNumber=0, cityName="") { + if (workerScript.checkingRam) { + return updateStaticRam("travel", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("travel", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.travel(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + return Player.sleeves[sleeveNumber].travel(Player, cityName); + }, + workForCompany : function(sleeveNumber=0, companyName="") { + if (workerScript.checkingRam) { + return updateStaticRam("workForCompany", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("workForCompany", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.workForCompany(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + return Player.sleeves[sleeveNumber].workForCompany(Player, companyName); + }, + workForFaction : function(sleeveNumber=0, factionName="", workType="") { + if (workerScript.checkingRam) { + return updateStaticRam("workForFaction", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("workForFaction", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.workForFaction(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + return Player.sleeves[sleeveNumber].workForFaction(Player, factionName, workType); + }, + workoutAtGym : function(sleeveNumber=0, gymName="", stat="") { + if (workerScript.checkingRam) { + return updateStaticRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.workoutAtGym(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + return Player.sleeves[sleeveNumber].workoutAtGym(Player, gymName, stat); + }, + } // End sleeve } //End return } //End NetscriptFunction() From 060e0f7bfc37199680375f32e0facea84200150c Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 03:46:54 -0400 Subject: [PATCH 20/34] get sync/shock --- src/NetscriptFunctions.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 0f45bf5dc..eaada6ee0 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -4828,6 +4828,18 @@ function NetscriptFunctions(workerScript) { return Player.sleeves[sleeveNumber].shockRecovery(Player); }, + getShock : function(sleeveNumber=0) { + if (workerScript.checkingRam) { + return updateStaticRam("getShock", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("getShock", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.getShock(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + return Player.sleeves[sleeveNumber].shock; + }, synchronize : function(sleeveNumber=0) { if (workerScript.checkingRam) { return updateStaticRam("synchronize", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4840,6 +4852,18 @@ function NetscriptFunctions(workerScript) { return Player.sleeves[sleeveNumber].synchronize(Player); }, + getSync : function(sleeveNumber=0) { + if (workerScript.checkingRam) { + return updateStaticRam("getSync", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("getSync", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.getSync(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + return Player.sleeves[sleeveNumber].sync; + }, commitCrime : function(sleeveNumber=0, crimeName="") { if (workerScript.checkingRam) { return updateStaticRam("commitCrime", CONSTANTS.ScriptSleeveBaseRamCost); From 9f715020df63db5d59eca8c90f044367b93ff9f2 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 03:53:29 -0400 Subject: [PATCH 21/34] syntax highlight some sleeve api --- src/ScriptEditor/AceNetscriptMode.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ScriptEditor/AceNetscriptMode.js b/src/ScriptEditor/AceNetscriptMode.js index f67e9e6a5..3c53ac4b1 100644 --- a/src/ScriptEditor/AceNetscriptMode.js +++ b/src/ScriptEditor/AceNetscriptMode.js @@ -125,7 +125,12 @@ let NetscriptFunctions = // Coding Contract API "codingcontract|attempt|getContractType|getData|getDescription|" + - "getNumTriesRemaining"; + "getNumTriesRemaining|" + + + // Sleeve API + "sleeve|getNumSleeves|shockRecovery|getShock|synchronize|" + + "getSync|commitCrime|takeUniversityCourse|travel|" + + "workForCompany|workForFaction|workoutAtGym"; var NetscriptHighlightRules = function(options) { var keywordMapper = this.createKeywordMapper({ From f8f4299ed5cb4cf1b296d0a456d38be985e1e556 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 18:10:12 -0400 Subject: [PATCH 22/34] getInfo and getTask sleeve api --- src/NetscriptFunctions.js | 137 ++++++++++++++++++++++----- src/ScriptEditor/AceNetscriptMode.js | 6 +- 2 files changed, 115 insertions(+), 28 deletions(-) diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index eaada6ee0..4d573ae2e 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -28,6 +28,7 @@ import { Factions, factionExists } from "./Faction/Factions"; import { joinFaction, purchaseAugmentation } from "./Faction/FactionHelpers"; +import { FactionWorkType } from "./Faction/FactionWorkTypeEnum"; import { getCostOfNextHacknetNode, purchaseHacknet } from "./HacknetNode"; import {Locations} from "./Locations"; @@ -71,6 +72,7 @@ import {WorkerScript, workerScripts, import {makeRuntimeRejectMsg, netscriptDelay, runScriptFromScript} from "./NetscriptEvaluator"; import {NetscriptPort} from "./NetscriptPort"; +import {SleeveTaskType} from "./PersonObjects/Sleeve/SleeveTaskTypesEnum" import {Page, routing} from "./ui/navigationTracking"; import {numeralWrapper} from "./ui/numeralFormat"; @@ -4809,7 +4811,7 @@ function NetscriptFunctions(workerScript) { }, }, // End coding contracts sleeve : { - getNumSleeves: function() { + getNumSleeves : function() { if (workerScript.checkingRam) { return updateStaticRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost); } @@ -4828,18 +4830,6 @@ function NetscriptFunctions(workerScript) { return Player.sleeves[sleeveNumber].shockRecovery(Player); }, - getShock : function(sleeveNumber=0) { - if (workerScript.checkingRam) { - return updateStaticRam("getShock", CONSTANTS.ScriptSleeveBaseRamCost); - } - updateDynamicRam("getShock", CONSTANTS.ScriptSleeveBaseRamCost); - if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { - workerScript.log(`ERROR: sleeve.getShock(${sleeveNumber}) failed because it is an invalid sleeve number.`); - return false; - } - - return Player.sleeves[sleeveNumber].shock; - }, synchronize : function(sleeveNumber=0) { if (workerScript.checkingRam) { return updateStaticRam("synchronize", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4852,18 +4842,6 @@ function NetscriptFunctions(workerScript) { return Player.sleeves[sleeveNumber].synchronize(Player); }, - getSync : function(sleeveNumber=0) { - if (workerScript.checkingRam) { - return updateStaticRam("getSync", CONSTANTS.ScriptSleeveBaseRamCost); - } - updateDynamicRam("getSync", CONSTANTS.ScriptSleeveBaseRamCost); - if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { - workerScript.log(`ERROR: sleeve.getSync(${sleeveNumber}) failed because it is an invalid sleeve number.`); - return false; - } - - return Player.sleeves[sleeveNumber].sync; - }, commitCrime : function(sleeveNumber=0, crimeName="") { if (workerScript.checkingRam) { return updateStaticRam("commitCrime", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4936,6 +4914,115 @@ function NetscriptFunctions(workerScript) { return Player.sleeves[sleeveNumber].workoutAtGym(Player, gymName, stat); }, + getStats : function(sleeveNumber=0) { + if (workerScript.checkingRam) { + return updateStaticRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.workoutAtGym(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + const sl = Player.sleeves[i]; + return { + shock: sl.shock, + sync: sl.sync, + hacking_skill: sl.hacking_skill, + strength: sl.strength, + defense: sl.defense, + dexterity: sl.dexterity, + agility: sl.agility, + charisma: sl.charisma, + }; + }, + getTask : function(sleeveNumber=0) { + if (workerScript.checkingRam) { + return updateStaticRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.getTask(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + const sl = Player.sleeves[sleeveNumber]; + return { + task: SleeveTaskType[sl.currentTask], + crime: sl.crimeType, + location: sl.currentTaskLocation, + gymStatType: sl.gymStatType, + factionWorkType: FactionWorkType[sl.factionWorkType], + }; + }, + getInformation : function(sleeveNumber=0) { + if (workerScript.checkingRam) { + return updateStaticRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost); + } + updateDynamicRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost); + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + workerScript.log(`ERROR: sleeve.getInformation(${sleeveNumber}) failed because it is an invalid sleeve number.`); + return false; + } + + const sl = Player.sleeves[sleeveNumber]; + return { + city: sl.city, + hp: sl.hp, + jobs: Object.keys(Player.jobs), // technically sleeves have the same jobs as the player. + jobTitle: Object.values(Player.jobs), + maxHp: sl.max_hp, + tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), // There's no reason not to give that infomation here as well. Worst case scenario it isn't used. + + mult: { + agility: sl.agility_mult, + agilityExp: sl.agility_exp_mult, + companyRep: sl.company_rep_mult, + crimeMoney: sl.crime_money_mult, + crimeSuccess: sl.crime_success_mult, + defense: sl.defense_mult, + defenseExp: sl.defense_exp_mult, + dexterity: sl.dexterity_mult, + dexterityExp: sl.dexterity_exp_mult, + factionRep: sl.faction_rep_mult, + hacking: sl.hacking_mult, + hackingExp: sl.hacking_exp_mult, + strength: sl.strength_mult, + strengthExp: sl.strength_exp_mult, + workMoney: sl.work_money_mult, + }, + + timeWorked: sl.currentTaskTime, + earningsForSleeves : { + workHackExpGain: sl.earningsForSleeves.hack, + workStrExpGain: sl.earningsForSleeves.str, + workDefExpGain: sl.earningsForSleeves.def, + workDexExpGain: sl.earningsForSleeves.dex, + workAgiExpGain: sl.earningsForSleeves.agi, + workChaExpGain: sl.earningsForSleeves.cha, + workMoneyGain: sl.earningsForSleeves.money, + }, + earningsForPlayer : { + workHackExpGain: sl.earningsForPlayer.hack, + workStrExpGain: sl.earningsForPlayer.str, + workDefExpGain: sl.earningsForPlayer.def, + workDexExpGain: sl.earningsForPlayer.dex, + workAgiExpGain: sl.earningsForPlayer.agi, + workChaExpGain: sl.earningsForPlayer.cha, + workMoneyGain: sl.earningsForPlayer.money, + }, + earningsForTask : { + workHackExpGain: sl.earningsForTask.hack, + workStrExpGain: sl.earningsForTask.str, + workDefExpGain: sl.earningsForTask.def, + workDexExpGain: sl.earningsForTask.dex, + workAgiExpGain: sl.earningsForTask.agi, + workChaExpGain: sl.earningsForTask.cha, + workMoneyGain: sl.earningsForTask.money, + }, + workRepGain: sl.getRepGain(), + } + }, } // End sleeve } //End return } //End NetscriptFunction() diff --git a/src/ScriptEditor/AceNetscriptMode.js b/src/ScriptEditor/AceNetscriptMode.js index 3c53ac4b1..70d7de56f 100644 --- a/src/ScriptEditor/AceNetscriptMode.js +++ b/src/ScriptEditor/AceNetscriptMode.js @@ -128,9 +128,9 @@ let NetscriptFunctions = "getNumTriesRemaining|" + // Sleeve API - "sleeve|getNumSleeves|shockRecovery|getShock|synchronize|" + - "getSync|commitCrime|takeUniversityCourse|travel|" + - "workForCompany|workForFaction|workoutAtGym"; + "sleeve|getNumSleeves|shockRecovery|synchronize|commitCrime|" + + "takeUniversityCourse|travel|workForCompany|workForFaction|workoutAtGym|" + + "getStats|getTask|getInformation"; var NetscriptHighlightRules = function(options) { var keywordMapper = this.createKeywordMapper({ From b132efbdedec08bffbdfbdb960b1b9476662a3f1 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 19:05:38 -0400 Subject: [PATCH 23/34] doc for sleeve api --- doc/source/advancedgameplay/sourcefiles.rst | 1 + doc/source/netscript.rst | 1 + doc/source/netscript/netscriptsleeveapi.rst | 65 +++++++++++++++++++ .../netscript/sleeveapi/commitCrime.rst | 11 ++++ .../netscript/sleeveapi/getInformation.rst | 65 +++++++++++++++++++ .../netscript/sleeveapi/getNumSleeves.rst | 6 ++ doc/source/netscript/sleeveapi/getStats.rst | 21 ++++++ doc/source/netscript/sleeveapi/getTask.rst | 18 +++++ .../netscript/sleeveapi/shockRecovery.rst | 8 +++ .../netscript/sleeveapi/synchronize.rst | 8 +++ .../sleeveapi/takeUniversityCourse.rst | 10 +++ doc/source/netscript/sleeveapi/travel.rst | 9 +++ .../netscript/sleeveapi/workForCompany.rst | 9 +++ .../netscript/sleeveapi/workForFaction.rst | 10 +++ .../netscript/sleeveapi/workoutAtGym.rst | 10 +++ src/NetscriptFunctions.js | 38 +++++++++++ 16 files changed, 290 insertions(+) create mode 100644 doc/source/netscript/netscriptsleeveapi.rst create mode 100644 doc/source/netscript/sleeveapi/commitCrime.rst create mode 100644 doc/source/netscript/sleeveapi/getInformation.rst create mode 100644 doc/source/netscript/sleeveapi/getNumSleeves.rst create mode 100644 doc/source/netscript/sleeveapi/getStats.rst create mode 100644 doc/source/netscript/sleeveapi/getTask.rst create mode 100644 doc/source/netscript/sleeveapi/shockRecovery.rst create mode 100644 doc/source/netscript/sleeveapi/synchronize.rst create mode 100644 doc/source/netscript/sleeveapi/takeUniversityCourse.rst create mode 100644 doc/source/netscript/sleeveapi/travel.rst create mode 100644 doc/source/netscript/sleeveapi/workForCompany.rst create mode 100644 doc/source/netscript/sleeveapi/workForFaction.rst create mode 100644 doc/source/netscript/sleeveapi/workoutAtGym.rst diff --git a/doc/source/advancedgameplay/sourcefiles.rst b/doc/source/advancedgameplay/sourcefiles.rst index bc7d46c91..b33bca7fa 100644 --- a/doc/source/advancedgameplay/sourcefiles.rst +++ b/doc/source/advancedgameplay/sourcefiles.rst @@ -48,6 +48,7 @@ List of all Source-Files | BitNode-9: Coming Soon | | +------------------------------------+-------------------------------------------------------------------------------------+ | BitNode-10: Digital Carbon | * Each level of this grants a Duplicate Sleeve | +| | * Allows the player to access the :ref:`netscript_sleeveapi` in other BitNodes | +------------------------------------+-------------------------------------------------------------------------------------+ | BitNode-11: The Big Crash | * Company favor increases both the player's salary and reputation gain at that | | | company by 1% per favor (rather than just the reputation gain) | diff --git a/doc/source/netscript.rst b/doc/source/netscript.rst index f2f1b9aa0..073d38323 100644 --- a/doc/source/netscript.rst +++ b/doc/source/netscript.rst @@ -29,4 +29,5 @@ to reach out to the developer! Bladeburner API Gang API Coding Contract API + Sleeve API Miscellaneous diff --git a/doc/source/netscript/netscriptsleeveapi.rst b/doc/source/netscript/netscriptsleeveapi.rst new file mode 100644 index 000000000..8f776b7b8 --- /dev/null +++ b/doc/source/netscript/netscriptsleeveapi.rst @@ -0,0 +1,65 @@ +.. _netscript_sleeveapi: + +Netscript Sleeve API +========================= +Netscript provides the following API for interacting with the game's Sleeve mechanic. + +The Sleeve API is **not** immediately available to the player and must be unlocked +later in the game. + +**WARNING: This page contains spoilers for the game** + +The Sleeve API is unlocked in BitNode-10. If you are in BitNode-10, you will +automatically gain access to this API. Otherwise, you must have Source-File 10 in +order to use this API in other BitNodes + +**Sleeve API functions must be accessed through the 'sleeve' namespace** + +In :ref:`netscript1`:: + + sleeve.synchronize(0); + sleeve.commitCrime(0, "shoplift"); + +In :ref:`netscriptjs`:: + + ns.sleeve.synchronize(0); + ns.sleeve.commitCrime(0, "shoplift"); + +.. toctree:: + :caption: Functions: + + commitCrime() + getNumSleeves() + getTask() + synchronize() + travel() + workForFaction() + getInformation() + getStats() + shockRecovery() + takeUniversityCourse() + workForCompany() + workoutAtGym() + + +Examples +-------- + +**Basic example usage**:: + + for(let i = 0; i < sleeve.getNumSleeves(); i++) { + sleeve.shockRecovery(i); + } + + await sleep(10*60*60); // wait 10h + + for(let i = 0; i < sleeve.getNumSleeves(); i++) { + sleeve.synchronize(i); + } + + await sleep(10*60*60); // wait 10h + + for(let i = 0; i < sleeve.getNumSleeves(); i++) { + sleeve.commitCrime(i, 'shoplift'); + } + diff --git a/doc/source/netscript/sleeveapi/commitCrime.rst b/doc/source/netscript/sleeveapi/commitCrime.rst new file mode 100644 index 000000000..7b8652f8a --- /dev/null +++ b/doc/source/netscript/sleeveapi/commitCrime.rst @@ -0,0 +1,11 @@ +commitCrime() Netscript Function +======================================= + +.. js:function:: commitCrime(sleeveNumber, name) + + :param int sleeveNumber: index of the sleeve to start commiting crime. + :param string name: Name of the crime. Must be an exact match. + + Return a boolean indicating whether or not this action was set successfully. + + Returns false if an invalid action is specified. diff --git a/doc/source/netscript/sleeveapi/getInformation.rst b/doc/source/netscript/sleeveapi/getInformation.rst new file mode 100644 index 000000000..de11ec982 --- /dev/null +++ b/doc/source/netscript/sleeveapi/getInformation.rst @@ -0,0 +1,65 @@ +getInformation() Netscript Function +======================================= + +.. js:function:: getInformation(sleeveNumber) + + :param int sleeveNumber: index of the sleeve to retrieve information. + + Return a struct containing tons of information about this sleeve + +.. code-block:: javascript + + { + city: location of the sleeve, + hp: current hp of the sleeve, + maxHp: max hp of the sleeve, + jobs: jobs available to the sleeve, + jobTitle: job titles available to the sleeve, + tor: does this sleeve have access to the tor router, + mult: { + agility: agility multiplier, + agilityExp: agility exp multiplier, + companyRep: company reputation multiplier, + crimeMoney: crime money multiplier, + crimeSuccess: crime success chance multiplier, + defense: defense multiplier, + defenseExp: defense exp multiplier, + dexterity: dexterity multiplier, + dexterityExp: dexterity exp multiplier, + factionRep: faction reputation multiplier, + hacking: hacking skill multiplier, + hackingExp: hacking exp multiplier, + strength: strength multiplier, + strengthExp: strength exp multiplier, + workMoney: work money multiplier, + }, + timeWorked: time spent on the current task in milliseconds, + earningsForSleeves : { earnings synchronized to other sleeves + workHackExpGain: hacking exp gained from work, + workStrExpGain: strength exp gained from work, + workDefExpGain: defense exp gained from work, + workDexExpGain: dexterity exp gained from work, + workAgiExpGain: agility exp gained from work, + workChaExpGain: charisma exp gained from work, + workMoneyGain: money gained from work, + }, + earningsForPlayer : { earnings synchronized to the player + workHackExpGain: hacking exp gained from work, + workStrExpGain: strength exp gained from work, + workDefExpGain: defense exp gained from work, + workDexExpGain: dexterity exp gained from work, + workAgiExpGain: agility exp gained from work, + workChaExpGain: charisma exp gained from work, + workMoneyGain: money gained from work, + }, + earningsForTask : { earnings for this sleeve + workHackExpGain: hacking exp gained from work, + workStrExpGain: strength exp gained from work, + workDefExpGain: defense exp gained from work, + workDexExpGain: dexterity exp gained from work, + workAgiExpGain: agility exp gained from work, + workChaExpGain: charisma exp gained from work, + workMoneyGain: money gained from work, + }, + workRepGain: sl.getRepGain(), + } diff --git a/doc/source/netscript/sleeveapi/getNumSleeves.rst b/doc/source/netscript/sleeveapi/getNumSleeves.rst new file mode 100644 index 000000000..25640992c --- /dev/null +++ b/doc/source/netscript/sleeveapi/getNumSleeves.rst @@ -0,0 +1,6 @@ +getNumSleeves() Netscript Function +======================================= + +.. js:function:: getNumSleeves() + + Return the number of duplicate sleeves the player has. diff --git a/doc/source/netscript/sleeveapi/getStats.rst b/doc/source/netscript/sleeveapi/getStats.rst new file mode 100644 index 000000000..f121ae475 --- /dev/null +++ b/doc/source/netscript/sleeveapi/getStats.rst @@ -0,0 +1,21 @@ +getStats() Netscript Function +======================================= + +.. js:function:: getStatus(sleeveNumber) + + :param int sleeveNumber: index of the sleeve to get stats of. + + Return a structure containing the stats of the sleeve + +.. code-block:: javascript + + { + shock: current shock of the sleeve [0-1], + sync: current sync of the sleeve [0-1], + hacking_skill: current hacking skill of the sleeve, + strength: current strength of the sleeve, + defense: current defense of the sleeve, + dexterity: current dexterity of the sleeve, + agility: current agility of the sleeve, + charisma: current charisma of the sleeve, + } diff --git a/doc/source/netscript/sleeveapi/getTask.rst b/doc/source/netscript/sleeveapi/getTask.rst new file mode 100644 index 000000000..e380da629 --- /dev/null +++ b/doc/source/netscript/sleeveapi/getTask.rst @@ -0,0 +1,18 @@ +getTask() Netscript Function +======================================= + +.. js:function:: getTask(sleeveNumber) + + :param int sleeveNumber: index of the sleeve to retrieve task from. + + Return the current task that the sleeve is performing. type is set to "Idle" if the sleeve isn't doing anything + +.. code-block:: javascript + + { + task: number, // task type + crime: number, // crime currently attempting, if any + location: number, // location of the task, if any + gymStatType: number, // stat being trained at the gym, if any + factionWorkType: number, // faction work type being performed, if any + } diff --git a/doc/source/netscript/sleeveapi/shockRecovery.rst b/doc/source/netscript/sleeveapi/shockRecovery.rst new file mode 100644 index 000000000..d176030f7 --- /dev/null +++ b/doc/source/netscript/sleeveapi/shockRecovery.rst @@ -0,0 +1,8 @@ +shockRecovery() Netscript Function +======================================= + +.. js:function:: shockRecovery(sleeveNumber) + + :param int sleeveNumber: index of the sleeve to start recovery. + + Return a boolean indicating whether or not this action was set successfully. \ No newline at end of file diff --git a/doc/source/netscript/sleeveapi/synchronize.rst b/doc/source/netscript/sleeveapi/synchronize.rst new file mode 100644 index 000000000..41af0165d --- /dev/null +++ b/doc/source/netscript/sleeveapi/synchronize.rst @@ -0,0 +1,8 @@ +synchronize() Netscript Function +======================================= + +.. js:function:: synchronize(sleeveNumber) + + :param int sleeveNumber: index of the sleeve to start synchronizing. + + Return a boolean indicating whether or not this action was set successfully. \ No newline at end of file diff --git a/doc/source/netscript/sleeveapi/takeUniversityCourse.rst b/doc/source/netscript/sleeveapi/takeUniversityCourse.rst new file mode 100644 index 000000000..9a87648de --- /dev/null +++ b/doc/source/netscript/sleeveapi/takeUniversityCourse.rst @@ -0,0 +1,10 @@ +takeUniversityCourse() Netscript Function +======================================= + +.. js:function:: takeUniversityCourse(sleeveNumber, university, className) + + :param int sleeveNumber: index of the sleeve to start taking class. + :param string university: name of the university to attend. + :param string className: name of the class to follow. + + Return a boolean indicating whether or not this action was set successfully. \ No newline at end of file diff --git a/doc/source/netscript/sleeveapi/travel.rst b/doc/source/netscript/sleeveapi/travel.rst new file mode 100644 index 000000000..fdae988eb --- /dev/null +++ b/doc/source/netscript/sleeveapi/travel.rst @@ -0,0 +1,9 @@ +travel() Netscript Function +======================================= + +.. js:function:: travel(sleeveNumber, cityName) + + :param int sleeveNumber: index of the sleeve to travel. + :param string cityName: name of the destination city. + + Return a boolean indicating whether or not the sleeve reached destination. \ No newline at end of file diff --git a/doc/source/netscript/sleeveapi/workForCompany.rst b/doc/source/netscript/sleeveapi/workForCompany.rst new file mode 100644 index 000000000..301bc04a7 --- /dev/null +++ b/doc/source/netscript/sleeveapi/workForCompany.rst @@ -0,0 +1,9 @@ +workForCompany() Netscript Function +======================================= + +.. js:function:: workForCompany(sleeveNumber, companyName) + + :param int sleeveNumber: index of the sleeve to work for the company. + :param string companyName: name of the company to work for. + + Return a boolean indicating whether or not the sleeve started working or this company. \ No newline at end of file diff --git a/doc/source/netscript/sleeveapi/workForFaction.rst b/doc/source/netscript/sleeveapi/workForFaction.rst new file mode 100644 index 000000000..2b94e99dd --- /dev/null +++ b/doc/source/netscript/sleeveapi/workForFaction.rst @@ -0,0 +1,10 @@ +workForFaction() Netscript Function +======================================= + +.. js:function:: workForFaction(sleeveNumber, factionName, factionWorkType) + + :param int sleeveNumber: index of the sleeve to work for the faction. + :param string factionName: name of the faction to work for. + :param string factionWorkType: name of the action to perform for this faction. + + Return a boolean indicating whether or not the sleeve started working or this faction. \ No newline at end of file diff --git a/doc/source/netscript/sleeveapi/workoutAtGym.rst b/doc/source/netscript/sleeveapi/workoutAtGym.rst new file mode 100644 index 000000000..f1207bc7a --- /dev/null +++ b/doc/source/netscript/sleeveapi/workoutAtGym.rst @@ -0,0 +1,10 @@ +workoutAtGym() Netscript Function +======================================= + +.. js:function:: workoutAtGym(sleeveNumber, gymName, stat) + + :param int sleeveNumber: index of the sleeve to workout at the gym. + :param string gymName: name of the gym. + :param string stat: name of the stat to train. + + Return a boolean indicating whether or not the sleeve started working out. \ No newline at end of file diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 4d573ae2e..2a06d7747 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -97,6 +97,7 @@ var hasCorporationSF = false, //Source-File 3 hasBladeburnerSF = false, //Source-File 6 hasBladeburner2079SF = false, //Source-File 7 hasWallStreetSF = false, //Source-File 8 + hasSleeveSF = false, //Source-File 10 hasBn11SF = false; //Source-File 11 var singularitySFLvl=1, wallStreetSFLvl=1; @@ -187,6 +188,7 @@ function initSingularitySFFlags() { hasWallStreetSF = true; wallStreetSFLvl = Player.sourceFiles[i].lvl; } + if (Player.sourceFiles[i].n === 10) {hasSleeveSF = true;} if (Player.sourceFiles[i].n === 11) {hasBn11SF = true;} } } @@ -4815,6 +4817,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "getNumSleeves() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost); return Player.sleeves.length; }, @@ -4822,6 +4827,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("shockRecovery", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "shockRecovery() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("shockRecovery", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { workerScript.log(`ERROR: sleeve.shockRecovery(${sleeveNumber}) failed because it is an invalid sleeve number.`); @@ -4834,6 +4842,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("synchronize", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "synchronize() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("synchronize", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { workerScript.log(`ERROR: sleeve.synchronize(${sleeveNumber}) failed because it is an invalid sleeve number.`); @@ -4846,6 +4857,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("commitCrime", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "commitCrime() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("commitCrime", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { workerScript.log(`ERROR: sleeve.commitCrime(${sleeveNumber}) failed because it is an invalid sleeve number.`); @@ -4858,6 +4872,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("takeUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "takeUniversityCourse() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("takeUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { workerScript.log(`ERROR: sleeve.takeUniversityCourse(${sleeveNumber}) failed because it is an invalid sleeve number.`); @@ -4870,6 +4887,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("travel", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "travel() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("travel", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { workerScript.log(`ERROR: sleeve.travel(${sleeveNumber}) failed because it is an invalid sleeve number.`); @@ -4882,6 +4902,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("workForCompany", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "workForCompany() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("workForCompany", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { workerScript.log(`ERROR: sleeve.workForCompany(${sleeveNumber}) failed because it is an invalid sleeve number.`); @@ -4894,6 +4917,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("workForFaction", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "workForFaction() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("workForFaction", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { workerScript.log(`ERROR: sleeve.workForFaction(${sleeveNumber}) failed because it is an invalid sleeve number.`); @@ -4906,6 +4932,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "workoutAtGym() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { workerScript.log(`ERROR: sleeve.workoutAtGym(${sleeveNumber}) failed because it is an invalid sleeve number.`); @@ -4918,6 +4947,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "getStats() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { workerScript.log(`ERROR: sleeve.workoutAtGym(${sleeveNumber}) failed because it is an invalid sleeve number.`); @@ -4940,6 +4972,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "getTask() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { workerScript.log(`ERROR: sleeve.getTask(${sleeveNumber}) failed because it is an invalid sleeve number.`); @@ -4959,6 +4994,9 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost); } + if (Player.bitNodeN !== 10 && !hasSleeveSF) { + throw makeRuntimeRejectMsg(workerScript, "getInformation() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + } updateDynamicRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { workerScript.log(`ERROR: sleeve.getInformation(${sleeveNumber}) failed because it is an invalid sleeve number.`); From 261abdea78faa0e08f6292c4acd4555d711add6a Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 19:32:20 -0400 Subject: [PATCH 24/34] typo in sleeve getTask doc --- doc/source/netscript/sleeveapi/getTask.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/netscript/sleeveapi/getTask.rst b/doc/source/netscript/sleeveapi/getTask.rst index e380da629..025ee045a 100644 --- a/doc/source/netscript/sleeveapi/getTask.rst +++ b/doc/source/netscript/sleeveapi/getTask.rst @@ -10,9 +10,9 @@ getTask() Netscript Function .. code-block:: javascript { - task: number, // task type - crime: number, // crime currently attempting, if any - location: number, // location of the task, if any - gymStatType: number, // stat being trained at the gym, if any - factionWorkType: number, // faction work type being performed, if any + task: string, // task type + crime: string, // crime currently attempting, if any + location: string, // location of the task, if any + gymStatType: string, // stat being trained at the gym, if any + factionWorkType: string, // faction work type being performed, if any } From 08136524bea3681da199be37fc1699daf0e5f68e Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 12 Mar 2019 19:35:44 -0400 Subject: [PATCH 25/34] use SourceFileFlags instead of hasSleeveSF --- src/NetscriptFunctions.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 2a06d7747..a417f199e 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -61,6 +61,7 @@ import {StockMarket, StockSymbols, SymbolToStockMap, PositionTypes, placeOrder, cancelOrder} from "./StockMarket/StockMarket"; import { getStockmarket4SDataCost, getStockMarket4STixApiCost } from "./StockMarket/StockMarketCosts"; +import { SourceFileFlags } from "./SourceFile/SourceFileFlags" import {TextFile, getTextFile, createTextFile} from "./TextFile"; import {unknownBladeburnerActionErrorMessage, @@ -97,7 +98,6 @@ var hasCorporationSF = false, //Source-File 3 hasBladeburnerSF = false, //Source-File 6 hasBladeburner2079SF = false, //Source-File 7 hasWallStreetSF = false, //Source-File 8 - hasSleeveSF = false, //Source-File 10 hasBn11SF = false; //Source-File 11 var singularitySFLvl=1, wallStreetSFLvl=1; @@ -188,7 +188,6 @@ function initSingularitySFFlags() { hasWallStreetSF = true; wallStreetSFLvl = Player.sourceFiles[i].lvl; } - if (Player.sourceFiles[i].n === 10) {hasSleeveSF = true;} if (Player.sourceFiles[i].n === 11) {hasBn11SF = true;} } } @@ -4817,7 +4816,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "getNumSleeves() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4827,7 +4826,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("shockRecovery", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "shockRecovery() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("shockRecovery", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4842,7 +4841,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("synchronize", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "synchronize() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("synchronize", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4857,7 +4856,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("commitCrime", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "commitCrime() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("commitCrime", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4872,7 +4871,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("takeUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "takeUniversityCourse() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("takeUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4887,7 +4886,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("travel", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "travel() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("travel", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4902,7 +4901,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("workForCompany", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "workForCompany() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("workForCompany", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4917,7 +4916,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("workForFaction", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "workForFaction() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("workForFaction", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4932,7 +4931,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "workoutAtGym() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4947,7 +4946,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "getStats() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4972,7 +4971,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "getTask() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost); @@ -4994,7 +4993,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.checkingRam) { return updateStaticRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost); } - if (Player.bitNodeN !== 10 && !hasSleeveSF) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { throw makeRuntimeRejectMsg(workerScript, "getInformation() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } updateDynamicRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost); From c973663dc2b4e92dd48b8311b7c48062acf10d10 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sun, 17 Mar 2019 18:20:31 -0700 Subject: [PATCH 26/34] Fixed empty td Element issue with new React Character overview. Updated changelog --- src/Constants.ts | 3 + .../ui/CorporationUIEventHandler.js | 10 ++- src/ui/React/CharacterOverview.jsx | 80 +++++++++---------- 3 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/Constants.ts b/src/Constants.ts index 1d48c94a2..be3018275 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -289,6 +289,8 @@ export let CONSTANTS: IMap = { ** Increased the cost multiplier for upgrading office size (the cost will increase faster) ** The stats of your employees now has a slightly larger effect on production & sales ** Added several new Research upgrades + ** Market-TA research now allows you to automatically set sale price at optimal values + ** Market-TA research now works for Products (not just Materials) ** Reduced the amount of Scientific Research needed to unlock the Hi-Tech R&D Laboratory from 10k to 5k ** Energy Material requirement of the Software industry reduced from 1 to 0.5 ** It is now slightly easier to increase the Software industry's production multiplier @@ -299,6 +301,7 @@ export let CONSTANTS: IMap = { ** Training employees is now 3x more effective ** Bug Fix: An industry's products are now properly separated between different cities + * Added a Netscript API for Duplicate Sleeves (by hydroflame) * Rebalanced BitNode-3 to make it slightly harder * Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina * Bug Fix: The cost of purchasing Augmentations for Duplicate Sleeves no longer scales with how many Augs you've purchased for yourself diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js index a43d78819..713ad825f 100644 --- a/src/Corporation/ui/CorporationUIEventHandler.js +++ b/src/Corporation/ui/CorporationUIEventHandler.js @@ -542,7 +542,9 @@ export class CorporationEventHandler { }); let confirmBtn; const input = createElement("input", { - type:"number", placeholder:"Limit", + margin: "5px", + placeholder:"Limit", + type:"number", onkeyup: (e) => { e.preventDefault(); if (e.keyCode === KEY.ENTER) { confirmBtn.click(); } @@ -550,9 +552,9 @@ export class CorporationEventHandler { }); confirmBtn = createElement("button", { class: "std-button", - display:"inline-block", - innerText:"Limit production", - margin:'6px', + display: "inline-block", + innerText: "Limit production", + margin: "5px", clickListener: () => { if (input.value === "") { product.prdman[city][0] = false; diff --git a/src/ui/React/CharacterOverview.jsx b/src/ui/React/CharacterOverview.jsx index 1bdc69c9c..a3594cc75 100644 --- a/src/ui/React/CharacterOverview.jsx +++ b/src/ui/React/CharacterOverview.jsx @@ -8,47 +8,47 @@ const Component = React.Component; export class CharacterOverviewComponent extends Component { render() { - let intelligence = ""; - if (Player.intelligence >= 1) { - intelligence=( - - Int: {(Player.intelligence).toLocaleString()} - -); - } + const intelligence = ( + + Int: {(Player.intelligence).toLocaleString()} + + ); return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - {intelligence} - -
Hp:{Player.hp + " / " + Player.max_hp}
Money: {numeralWrapper.format(Player.money.toNumber(), '$0.000a')}
Hack: {(Player.hacking_skill).toLocaleString()}
Str: {(Player.strength).toLocaleString()}
Def: {(Player.defense).toLocaleString()}
Dex: {(Player.dexterity).toLocaleString()}
Agi: {(Player.agility).toLocaleString()}
Cha: {(Player.charisma).toLocaleString()}
-
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + { + Player.intelligence >= 1 && + intelligence + } + +
Hp:{Player.hp + " / " + Player.max_hp}
Money: {numeralWrapper.format(Player.money.toNumber(), '$0.000a')}
Hack: {(Player.hacking_skill).toLocaleString()}
Str: {(Player.strength).toLocaleString()}
Def: {(Player.defense).toLocaleString()}
Dex: {(Player.dexterity).toLocaleString()}
Agi: {(Player.agility).toLocaleString()}
Cha: {(Player.charisma).toLocaleString()}
+
) } -} \ No newline at end of file +} From c3bc6a0c28577d870a6a5d7ada081b5cb83563c5 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sun, 17 Mar 2019 22:04:12 -0700 Subject: [PATCH 27/34] Changed Sleeve API functions to prevent conflicts with identically-named functions --- doc/source/advancedgameplay/sleeves.rst | 36 +++++++-- doc/source/conf.py | 4 +- .../netscript/netscriptadvancedfunctions.rst | 2 + .../netscript/netscriptbladeburnerapi.rst | 2 +- doc/source/netscript/netscriptgangapi.rst | 2 +- .../netscriptsingularityfunctions.rst | 4 +- doc/source/netscript/netscriptsleeveapi.rst | 49 +++++++----- .../netscript/sleeveapi/commitCrime.rst | 11 --- .../netscript/sleeveapi/getInformation.rst | 2 +- .../{getStats.rst => getSleeveStats.rst} | 6 +- doc/source/netscript/sleeveapi/getTask.rst | 2 +- .../netscript/sleeveapi/setToCommitCrime.rst | 11 +++ .../netscript/sleeveapi/setToCompanyWork.rst | 9 +++ .../netscript/sleeveapi/setToFactionWork.rst | 10 +++ .../netscript/sleeveapi/setToGymWorkout.rst | 10 +++ .../sleeveapi/setToShockRecovery.rst | 8 ++ .../netscript/sleeveapi/setToSynchronize.rst | 8 ++ .../sleeveapi/setToUniversityCourse.rst | 10 +++ .../netscript/sleeveapi/shockRecovery.rst | 8 -- .../netscript/sleeveapi/synchronize.rst | 8 -- .../sleeveapi/takeUniversityCourse.rst | 10 --- doc/source/netscript/sleeveapi/travel.rst | 6 +- .../netscript/sleeveapi/workForCompany.rst | 9 --- .../netscript/sleeveapi/workForFaction.rst | 10 --- .../netscript/sleeveapi/workoutAtGym.rst | 10 --- src/Constants.ts | 6 +- src/NetscriptFunctions.js | 74 +++++++++---------- src/ScriptEditor/AceNetscriptMode.js | 6 +- src/ScriptEditor/CodeMirrorNetscriptMode.js | 17 ++++- 29 files changed, 199 insertions(+), 151 deletions(-) delete mode 100644 doc/source/netscript/sleeveapi/commitCrime.rst rename doc/source/netscript/sleeveapi/{getStats.rst => getSleeveStats.rst} (72%) create mode 100644 doc/source/netscript/sleeveapi/setToCommitCrime.rst create mode 100644 doc/source/netscript/sleeveapi/setToCompanyWork.rst create mode 100644 doc/source/netscript/sleeveapi/setToFactionWork.rst create mode 100644 doc/source/netscript/sleeveapi/setToGymWorkout.rst create mode 100644 doc/source/netscript/sleeveapi/setToShockRecovery.rst create mode 100644 doc/source/netscript/sleeveapi/setToSynchronize.rst create mode 100644 doc/source/netscript/sleeveapi/setToUniversityCourse.rst delete mode 100644 doc/source/netscript/sleeveapi/shockRecovery.rst delete mode 100644 doc/source/netscript/sleeveapi/synchronize.rst delete mode 100644 doc/source/netscript/sleeveapi/takeUniversityCourse.rst delete mode 100644 doc/source/netscript/sleeveapi/workForCompany.rst delete mode 100644 doc/source/netscript/sleeveapi/workForFaction.rst delete mode 100644 doc/source/netscript/sleeveapi/workoutAtGym.rst diff --git a/doc/source/advancedgameplay/sleeves.rst b/doc/source/advancedgameplay/sleeves.rst index a3db25f26..b0db5b682 100644 --- a/doc/source/advancedgameplay/sleeves.rst +++ b/doc/source/advancedgameplay/sleeves.rst @@ -14,6 +14,8 @@ Sleeve technology unlocks two different gameplay features: Sleeve technology is unlocked in :ref:`BitNode-10 `. +.. _gameplay_duplicatesleeves: + Duplicate Sleeves ^^^^^^^^^^^^^^^^^ Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciuosness @@ -28,6 +30,19 @@ Sleeves are their own individuals, which means they each have their own experien When a sleeve earns experience, it earns experience for itself, the player's original consciousness, as well as all of the player's other sleeves. +Duplicate Sleeves are **not** reset when installing Augmentations, but they are reset +when switching BitNodes. + +Obtaining Duplicate Sleeves +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +There are two methods of obtaining Duplicate Sleeves: + +1. Destroy BitNode-10. Each completion give you one additional Duplicate Sleeve +2. Purchase Duplicate Sleeves from :ref:`the faction The Covenant `. + This is only available in BitNodes-10 and above, and is only available after defeating + BitNode-10 at least once. Sleeves purchased this way are **permanent** (they persist + through BitNodes). You can purchase up to 5 Duplicate Sleeves from The Covenant. + Synchronization ~~~~~~~~~~~~~~~ Synchronization is a measure of how aligned your consciousness is with that of your @@ -50,15 +65,20 @@ no shock. Shock affects the amount of experience earned by the sleeve. Sleeve shock slowly decreases over time. You can further increase the rate at which it decreases by assigning sleeves to the 'Shock Recovery' task. -Obtaining Duplicate Sleeves -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -There are two methods of obtaining Duplicate Sleeves: +Augmentations +~~~~~~~~~~~~~ +You can purchase :ref:`Augmentations ` for your Duplicate +Sleeves. In order to do this, the Sleeve's Shock must be at 0. Any Augmentation +that is currently available to you through a faction is also available for your +Duplicate Sleeves. There are a few Augmentations, such as NeuroFlux Governor and +Bladeburner-specific ones, that cannot be purchased for a Duplicate Sleeve. -1. Destroy BitNode-10. Each completion give you one additional Duplicate Sleeve -2. Purchase Duplicate Sleeves from :ref:`the faction The Covenant `. - This is only available in BitNodes-10 and above, and is only available after defeating - BitNode-10 at least once. Sleeves purchased this way are permanent. You can purchase - up to 5 Duplicate Sleeves from The Covenant. +When you purchase an Augmentation for a Duplicate Sleeve, it is instantly installed. +When this happens, the Sleeve's stats are instantly reset back to 0, similar to +when you normally install Augmentations. + +The cost of purchasing an Augmentation for a Duplicate Sleeve is **not** affected +by how many Augmentations you have purchased for yourself, and vice versa. Re-sleeving ^^^^^^^^^^^ diff --git a/doc/source/conf.py b/doc/source/conf.py index 46634b60b..10114564b 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project) # built documents. # # The short X.Y version. -version = '0.44' +version = '0.45' # The full version, including alpha/beta/rc tags. -release = '0.44.1' +release = '0.45.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/source/netscript/netscriptadvancedfunctions.rst b/doc/source/netscript/netscriptadvancedfunctions.rst index 60400e1f7..796494851 100644 --- a/doc/source/netscript/netscriptadvancedfunctions.rst +++ b/doc/source/netscript/netscriptadvancedfunctions.rst @@ -4,6 +4,8 @@ Netscript Advanced Functions These Netscript functions become relevant later on in the game. They are put on a separate page because they contain spoilers for the game. +.. warning:: This page contains spoilers for the game + .. toctree:: getBitNodeMultipliers() diff --git a/doc/source/netscript/netscriptbladeburnerapi.rst b/doc/source/netscript/netscriptbladeburnerapi.rst index 8a40b12c2..dbd487b73 100644 --- a/doc/source/netscript/netscriptbladeburnerapi.rst +++ b/doc/source/netscript/netscriptbladeburnerapi.rst @@ -7,7 +7,7 @@ Netscript provides the following API for interacting with the game's Bladeburner The Bladeburner API is **not** immediately available to the player and must be unlocked later in the game -**WARNING: This page contains spoilers for the game** +.. warning:: This page contains spoilers for the game The Bladeburner API is unlocked in BitNode-7. If you are in BitNode-7, you will automatically gain access to this API. Otherwise, you must have Source-File 7 in diff --git a/doc/source/netscript/netscriptgangapi.rst b/doc/source/netscript/netscriptgangapi.rst index 95c322845..9e4335b5f 100644 --- a/doc/source/netscript/netscriptgangapi.rst +++ b/doc/source/netscript/netscriptgangapi.rst @@ -6,7 +6,7 @@ Netscript provides the following API for interacting with the game's Gang mechan The Gang API is **not** immediately available to the player and must be unlocked later in the game -**WARNING: This page contains spoilers for the game** +.. warning:: This page contains spoilers for the game The Gang API is unlocked in BitNode-2. Currently, BitNode-2 is the only location where the Gang mechanic is accessible. This may change in the future diff --git a/doc/source/netscript/netscriptsingularityfunctions.rst b/doc/source/netscript/netscriptsingularityfunctions.rst index 413d44f60..d36d901ae 100644 --- a/doc/source/netscript/netscriptsingularityfunctions.rst +++ b/doc/source/netscript/netscriptsingularityfunctions.rst @@ -9,7 +9,7 @@ and creating programs. The Singularity Functions are **not** immediately available to the player and must be unlocked later in the game. -**WARNING: This page contains spoilers for the game**. +.. warning:: This page contains spoilers for the game The Singularity Functions are unlocked in BitNode-4. If you are in BitNode-4, then you will automatically have access to all of these functions. You can use the Singularity Functions in other BitNodes if and only if you have the Source-File for BitNode-4 (aka Source-File 4). Each level of @@ -20,7 +20,7 @@ Note that Singularity Functions require twice as much RAM outside of BitNode-4 .. toctree:: :caption: Functions: - + universityCourse() gymWorkout() travelToCity() diff --git a/doc/source/netscript/netscriptsleeveapi.rst b/doc/source/netscript/netscriptsleeveapi.rst index 8f776b7b8..17a7e48f3 100644 --- a/doc/source/netscript/netscriptsleeveapi.rst +++ b/doc/source/netscript/netscriptsleeveapi.rst @@ -2,12 +2,13 @@ Netscript Sleeve API ========================= -Netscript provides the following API for interacting with the game's Sleeve mechanic. +Netscript provides the following API for interacting with the game's +:ref:`Duplicate Sleeve ` mechanic. The Sleeve API is **not** immediately available to the player and must be unlocked later in the game. -**WARNING: This page contains spoilers for the game** +.. warning:: This page contains spoilers for the game The Sleeve API is unlocked in BitNode-10. If you are in BitNode-10, you will automatically gain access to this API. Otherwise, you must have Source-File 10 in @@ -26,40 +27,50 @@ In :ref:`netscriptjs`:: ns.sleeve.commitCrime(0, "shoplift"); .. toctree:: - :caption: Functions: + :caption: API Functions: - commitCrime() getNumSleeves() - getTask() - synchronize() - travel() - workForFaction() + getSleeveStats() getInformation() - getStats() - shockRecovery() - takeUniversityCourse() - workForCompany() - workoutAtGym() + getTask() + setToShockRecovery() + setToSynchronize() + setToCommitCrime() + setToFactionWork() + setToCompanyWork() + setToUniversityCourse() + setToGymWorkout() + travel() +.. _netscript_sleeveapi_referencingaduplicatesleeve: + +Referencing a Duplicate Sleeve +------------------------------ +Most of the functions in the Sleeve API perform an operation on a single Duplicate +Sleeve. In order to specify which Sleeve the operation should be performed on, +a numeric index is used as an identifier. The index should follow array-notation, such +that the first Duplicate Sleeve has an index of 0, the second Duplicate Sleeve has +an index of 1, and so on. + +The order of the Duplicate Sleeves matches the order on the UI page. Examples -------- **Basic example usage**:: - for(let i = 0; i < sleeve.getNumSleeves(); i++) { + for (var i = 0; i < sleeve.getNumSleeves(); i++) { sleeve.shockRecovery(i); } - await sleep(10*60*60); // wait 10h + sleep(10*60*60); // wait 10h - for(let i = 0; i < sleeve.getNumSleeves(); i++) { + for (var i = 0; i < sleeve.getNumSleeves(); i++) { sleeve.synchronize(i); } - await sleep(10*60*60); // wait 10h + sleep(10*60*60); // wait 10h - for(let i = 0; i < sleeve.getNumSleeves(); i++) { + for (var i = 0; i < sleeve.getNumSleeves(); i++) { sleeve.commitCrime(i, 'shoplift'); } - diff --git a/doc/source/netscript/sleeveapi/commitCrime.rst b/doc/source/netscript/sleeveapi/commitCrime.rst deleted file mode 100644 index 7b8652f8a..000000000 --- a/doc/source/netscript/sleeveapi/commitCrime.rst +++ /dev/null @@ -1,11 +0,0 @@ -commitCrime() Netscript Function -======================================= - -.. js:function:: commitCrime(sleeveNumber, name) - - :param int sleeveNumber: index of the sleeve to start commiting crime. - :param string name: Name of the crime. Must be an exact match. - - Return a boolean indicating whether or not this action was set successfully. - - Returns false if an invalid action is specified. diff --git a/doc/source/netscript/sleeveapi/getInformation.rst b/doc/source/netscript/sleeveapi/getInformation.rst index de11ec982..834b5a07f 100644 --- a/doc/source/netscript/sleeveapi/getInformation.rst +++ b/doc/source/netscript/sleeveapi/getInformation.rst @@ -3,7 +3,7 @@ getInformation() Netscript Function .. js:function:: getInformation(sleeveNumber) - :param int sleeveNumber: index of the sleeve to retrieve information. + :param int sleeveNumber: Index of the sleeve to retrieve information. See :ref:`here ` Return a struct containing tons of information about this sleeve diff --git a/doc/source/netscript/sleeveapi/getStats.rst b/doc/source/netscript/sleeveapi/getSleeveStats.rst similarity index 72% rename from doc/source/netscript/sleeveapi/getStats.rst rename to doc/source/netscript/sleeveapi/getSleeveStats.rst index f121ae475..f7c538dd8 100644 --- a/doc/source/netscript/sleeveapi/getStats.rst +++ b/doc/source/netscript/sleeveapi/getSleeveStats.rst @@ -1,9 +1,9 @@ -getStats() Netscript Function -======================================= +getSleeveStats() Netscript Function +=================================== .. js:function:: getStatus(sleeveNumber) - :param int sleeveNumber: index of the sleeve to get stats of. + :param int sleeveNumber: Index of the sleeve to get stats of. See :ref:`here ` Return a structure containing the stats of the sleeve diff --git a/doc/source/netscript/sleeveapi/getTask.rst b/doc/source/netscript/sleeveapi/getTask.rst index 025ee045a..ffe5707fa 100644 --- a/doc/source/netscript/sleeveapi/getTask.rst +++ b/doc/source/netscript/sleeveapi/getTask.rst @@ -3,7 +3,7 @@ getTask() Netscript Function .. js:function:: getTask(sleeveNumber) - :param int sleeveNumber: index of the sleeve to retrieve task from. + :param int sleeveNumber: Index of the sleeve to retrieve task from. See :ref:`here ` Return the current task that the sleeve is performing. type is set to "Idle" if the sleeve isn't doing anything diff --git a/doc/source/netscript/sleeveapi/setToCommitCrime.rst b/doc/source/netscript/sleeveapi/setToCommitCrime.rst new file mode 100644 index 000000000..d2d79d097 --- /dev/null +++ b/doc/source/netscript/sleeveapi/setToCommitCrime.rst @@ -0,0 +1,11 @@ +setToCommitCrime() Netscript Function +===================================== + +.. js:function:: setToCommitCrime(sleeveNumber, name) + + :param int sleeveNumber: Index of the sleeve to start commiting crime. See :ref:`here ` + :param string name: Name of the crime. Must be an exact match. + + Return a boolean indicating whether or not this action was set successfully. + + Returns false if an invalid action is specified. diff --git a/doc/source/netscript/sleeveapi/setToCompanyWork.rst b/doc/source/netscript/sleeveapi/setToCompanyWork.rst new file mode 100644 index 000000000..4078de78e --- /dev/null +++ b/doc/source/netscript/sleeveapi/setToCompanyWork.rst @@ -0,0 +1,9 @@ +setToCompanyWork() Netscript Function +===================================== + +.. js:function:: setToCompanyWork(sleeveNumber, companyName) + + :param int sleeveNumber: Index of the sleeve to work for the company. See :ref:`here ` + :param string companyName: Name of the company to work for. + + Return a boolean indicating whether or not the sleeve started working or this company. diff --git a/doc/source/netscript/sleeveapi/setToFactionWork.rst b/doc/source/netscript/sleeveapi/setToFactionWork.rst new file mode 100644 index 000000000..7df05aa99 --- /dev/null +++ b/doc/source/netscript/sleeveapi/setToFactionWork.rst @@ -0,0 +1,10 @@ +setToFactionWork() Netscript Function +===================================== + +.. js:function:: setToFactionWork(sleeveNumber, factionName, factionWorkType) + + :param int sleeveNumber: Index of the sleeve to work for the faction. See :ref:`here ` + :param string factionName: Name of the faction to work for. + :param string factionWorkType: Name of the action to perform for this faction. + + Return a boolean indicating whether or not the sleeve started working or this faction. diff --git a/doc/source/netscript/sleeveapi/setToGymWorkout.rst b/doc/source/netscript/sleeveapi/setToGymWorkout.rst new file mode 100644 index 000000000..4175b213f --- /dev/null +++ b/doc/source/netscript/sleeveapi/setToGymWorkout.rst @@ -0,0 +1,10 @@ +setToGymWorkout() Netscript Function +==================================== + +.. js:function:: setToGymWorkout(sleeveNumber, gymName, stat) + + :param int sleeveNumber: Index of the sleeve to workout at the gym. See :ref:`here ` + :param string gymName: Name of the gym. + :param string stat: Name of the stat to train. + + Return a boolean indicating whether or not the sleeve started working out. diff --git a/doc/source/netscript/sleeveapi/setToShockRecovery.rst b/doc/source/netscript/sleeveapi/setToShockRecovery.rst new file mode 100644 index 000000000..16950dd90 --- /dev/null +++ b/doc/source/netscript/sleeveapi/setToShockRecovery.rst @@ -0,0 +1,8 @@ +setToShockRecovery() Netscript Function +======================================= + +.. js:function:: setToShockRecovery(sleeveNumber) + + :param int sleeveNumber: Index of the sleeve to start recovery. See :ref:`here ` + + Return a boolean indicating whether or not this action was set successfully. diff --git a/doc/source/netscript/sleeveapi/setToSynchronize.rst b/doc/source/netscript/sleeveapi/setToSynchronize.rst new file mode 100644 index 000000000..c8d4aa9e0 --- /dev/null +++ b/doc/source/netscript/sleeveapi/setToSynchronize.rst @@ -0,0 +1,8 @@ +setToSynchronize() Netscript Function +===================================== + +.. js:function:: setToSynchronize(sleeveNumber) + + :param int sleeveNumber: Index of the sleeve to start synchronizing. See :ref:`here ` + + Return a boolean indicating whether or not this action was set successfully. diff --git a/doc/source/netscript/sleeveapi/setToUniversityCourse.rst b/doc/source/netscript/sleeveapi/setToUniversityCourse.rst new file mode 100644 index 000000000..22dbbad9d --- /dev/null +++ b/doc/source/netscript/sleeveapi/setToUniversityCourse.rst @@ -0,0 +1,10 @@ +setToUniversityCourse() Netscript Function +========================================== + +.. js:function:: setToUniversityCourse(sleeveNumber, university, className) + + :param int sleeveNumber: Index of the sleeve to start taking class. See :ref:`here ` + :param string university: Name of the university to attend. + :param string className: Name of the class to follow. + + Return a boolean indicating whether or not this action was set successfully. diff --git a/doc/source/netscript/sleeveapi/shockRecovery.rst b/doc/source/netscript/sleeveapi/shockRecovery.rst deleted file mode 100644 index d176030f7..000000000 --- a/doc/source/netscript/sleeveapi/shockRecovery.rst +++ /dev/null @@ -1,8 +0,0 @@ -shockRecovery() Netscript Function -======================================= - -.. js:function:: shockRecovery(sleeveNumber) - - :param int sleeveNumber: index of the sleeve to start recovery. - - Return a boolean indicating whether or not this action was set successfully. \ No newline at end of file diff --git a/doc/source/netscript/sleeveapi/synchronize.rst b/doc/source/netscript/sleeveapi/synchronize.rst deleted file mode 100644 index 41af0165d..000000000 --- a/doc/source/netscript/sleeveapi/synchronize.rst +++ /dev/null @@ -1,8 +0,0 @@ -synchronize() Netscript Function -======================================= - -.. js:function:: synchronize(sleeveNumber) - - :param int sleeveNumber: index of the sleeve to start synchronizing. - - Return a boolean indicating whether or not this action was set successfully. \ No newline at end of file diff --git a/doc/source/netscript/sleeveapi/takeUniversityCourse.rst b/doc/source/netscript/sleeveapi/takeUniversityCourse.rst deleted file mode 100644 index 9a87648de..000000000 --- a/doc/source/netscript/sleeveapi/takeUniversityCourse.rst +++ /dev/null @@ -1,10 +0,0 @@ -takeUniversityCourse() Netscript Function -======================================= - -.. js:function:: takeUniversityCourse(sleeveNumber, university, className) - - :param int sleeveNumber: index of the sleeve to start taking class. - :param string university: name of the university to attend. - :param string className: name of the class to follow. - - Return a boolean indicating whether or not this action was set successfully. \ No newline at end of file diff --git a/doc/source/netscript/sleeveapi/travel.rst b/doc/source/netscript/sleeveapi/travel.rst index fdae988eb..e89c2fe0c 100644 --- a/doc/source/netscript/sleeveapi/travel.rst +++ b/doc/source/netscript/sleeveapi/travel.rst @@ -3,7 +3,7 @@ travel() Netscript Function .. js:function:: travel(sleeveNumber, cityName) - :param int sleeveNumber: index of the sleeve to travel. - :param string cityName: name of the destination city. + :param int sleeveNumber: Index of the sleeve to travel. See :ref:`here ` + :param string cityName: Name of the destination city. - Return a boolean indicating whether or not the sleeve reached destination. \ No newline at end of file + Return a boolean indicating whether or not the sleeve reached destination. diff --git a/doc/source/netscript/sleeveapi/workForCompany.rst b/doc/source/netscript/sleeveapi/workForCompany.rst deleted file mode 100644 index 301bc04a7..000000000 --- a/doc/source/netscript/sleeveapi/workForCompany.rst +++ /dev/null @@ -1,9 +0,0 @@ -workForCompany() Netscript Function -======================================= - -.. js:function:: workForCompany(sleeveNumber, companyName) - - :param int sleeveNumber: index of the sleeve to work for the company. - :param string companyName: name of the company to work for. - - Return a boolean indicating whether or not the sleeve started working or this company. \ No newline at end of file diff --git a/doc/source/netscript/sleeveapi/workForFaction.rst b/doc/source/netscript/sleeveapi/workForFaction.rst deleted file mode 100644 index 2b94e99dd..000000000 --- a/doc/source/netscript/sleeveapi/workForFaction.rst +++ /dev/null @@ -1,10 +0,0 @@ -workForFaction() Netscript Function -======================================= - -.. js:function:: workForFaction(sleeveNumber, factionName, factionWorkType) - - :param int sleeveNumber: index of the sleeve to work for the faction. - :param string factionName: name of the faction to work for. - :param string factionWorkType: name of the action to perform for this faction. - - Return a boolean indicating whether or not the sleeve started working or this faction. \ No newline at end of file diff --git a/doc/source/netscript/sleeveapi/workoutAtGym.rst b/doc/source/netscript/sleeveapi/workoutAtGym.rst deleted file mode 100644 index f1207bc7a..000000000 --- a/doc/source/netscript/sleeveapi/workoutAtGym.rst +++ /dev/null @@ -1,10 +0,0 @@ -workoutAtGym() Netscript Function -======================================= - -.. js:function:: workoutAtGym(sleeveNumber, gymName, stat) - - :param int sleeveNumber: index of the sleeve to workout at the gym. - :param string gymName: name of the gym. - :param string stat: name of the stat to train. - - Return a boolean indicating whether or not the sleeve started working out. \ No newline at end of file diff --git a/src/Constants.ts b/src/Constants.ts index d61140f62..916ffce55 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -85,12 +85,12 @@ export let CONSTANTS: IMap = { ScriptGetPurchasedServerMaxRam: 0.05, ScriptRoundRamCost: 0.05, ScriptReadWriteRamCost: 1.0, - ScriptArbScriptRamCost: 1.0, //Functions that apply to all scripts regardless of args + ScriptArbScriptRamCost: 1.0, // Functions that apply to all scripts regardless of args ScriptGetScriptRamCost: 0.1, ScriptGetHackTimeRamCost: 0.05, ScriptGetFavorToDonate: 0.10, ScriptCodingContractBaseRamCost:10, - ScriptSleeveBaseRamCost: 0, // TODO: let big boss figure out balance. + ScriptSleeveBaseRamCost: 4, ScriptSingularityFn1RamCost: 1, ScriptSingularityFn2RamCost: 2, @@ -303,7 +303,7 @@ export let CONSTANTS: IMap = { ** Bug Fix: An industry's products are now properly separated between different cities * Added a Netscript API for Duplicate Sleeves (by hydroflame) - * Rebalanced BitNode-3 to make it slightly harder + * Modified BitNode-3's BN multipliers to make it slightly harder * Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina * Bug Fix: The cost of purchasing Augmentations for Duplicate Sleeves no longer scales with how many Augs you've purchased for yourself ` diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index a417f199e..74133fdf3 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -4822,61 +4822,61 @@ function NetscriptFunctions(workerScript) { updateDynamicRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost); return Player.sleeves.length; }, - shockRecovery : function(sleeveNumber=0) { + setToShockRecovery : function(sleeveNumber=0) { if (workerScript.checkingRam) { - return updateStaticRam("shockRecovery", CONSTANTS.ScriptSleeveBaseRamCost); + return updateStaticRam("setToShockRecovery", CONSTANTS.ScriptSleeveBaseRamCost); } if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { - throw makeRuntimeRejectMsg(workerScript, "shockRecovery() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + throw makeRuntimeRejectMsg(workerScript, "setToShockRecovery() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } - updateDynamicRam("shockRecovery", CONSTANTS.ScriptSleeveBaseRamCost); + updateDynamicRam("setToShockRecovery", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { - workerScript.log(`ERROR: sleeve.shockRecovery(${sleeveNumber}) failed because it is an invalid sleeve number.`); + workerScript.log(`ERROR: sleeve.setToShockRecovery(${sleeveNumber}) failed because it is an invalid sleeve number.`); return false; } return Player.sleeves[sleeveNumber].shockRecovery(Player); }, - synchronize : function(sleeveNumber=0) { + setToSynchronize : function(sleeveNumber=0) { if (workerScript.checkingRam) { - return updateStaticRam("synchronize", CONSTANTS.ScriptSleeveBaseRamCost); + return updateStaticRam("setToSynchronize", CONSTANTS.ScriptSleeveBaseRamCost); } if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { - throw makeRuntimeRejectMsg(workerScript, "synchronize() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + throw makeRuntimeRejectMsg(workerScript, "setToSynchronize() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } - updateDynamicRam("synchronize", CONSTANTS.ScriptSleeveBaseRamCost); + updateDynamicRam("setToSynchronize", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { - workerScript.log(`ERROR: sleeve.synchronize(${sleeveNumber}) failed because it is an invalid sleeve number.`); + workerScript.log(`ERROR: sleeve.setToSynchronize(${sleeveNumber}) failed because it is an invalid sleeve number.`); return false; } return Player.sleeves[sleeveNumber].synchronize(Player); }, - commitCrime : function(sleeveNumber=0, crimeName="") { + setToCommitCrime : function(sleeveNumber=0, crimeName="") { if (workerScript.checkingRam) { - return updateStaticRam("commitCrime", CONSTANTS.ScriptSleeveBaseRamCost); + return updateStaticRam("setToCommitCrime", CONSTANTS.ScriptSleeveBaseRamCost); } if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { - throw makeRuntimeRejectMsg(workerScript, "commitCrime() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + throw makeRuntimeRejectMsg(workerScript, "setToCommitCrime() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } - updateDynamicRam("commitCrime", CONSTANTS.ScriptSleeveBaseRamCost); + updateDynamicRam("setToCommitCrime", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { - workerScript.log(`ERROR: sleeve.commitCrime(${sleeveNumber}) failed because it is an invalid sleeve number.`); + workerScript.log(`ERROR: sleeve.setToCommitCrime(${sleeveNumber}) failed because it is an invalid sleeve number.`); return false; } return Player.sleeves[sleeveNumber].commitCrime(Player, crimeName); }, - takeUniversityCourse : function(sleeveNumber=0, universityName="", className="") { + setToUniversityCourse : function(sleeveNumber=0, universityName="", className="") { if (workerScript.checkingRam) { - return updateStaticRam("takeUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost); + return updateStaticRam("setToUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost); } if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { - throw makeRuntimeRejectMsg(workerScript, "takeUniversityCourse() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + throw makeRuntimeRejectMsg(workerScript, "setToUniversityCourse() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } - updateDynamicRam("takeUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost); + updateDynamicRam("setToUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { - workerScript.log(`ERROR: sleeve.takeUniversityCourse(${sleeveNumber}) failed because it is an invalid sleeve number.`); + workerScript.log(`ERROR: sleeve.setToUniversityCourse(${sleeveNumber}) failed because it is an invalid sleeve number.`); return false; } @@ -4897,52 +4897,52 @@ function NetscriptFunctions(workerScript) { return Player.sleeves[sleeveNumber].travel(Player, cityName); }, - workForCompany : function(sleeveNumber=0, companyName="") { + setToCompanyWork : function(sleeveNumber=0, companyName="") { if (workerScript.checkingRam) { - return updateStaticRam("workForCompany", CONSTANTS.ScriptSleeveBaseRamCost); + return updateStaticRam("setToCompanyWork", CONSTANTS.ScriptSleeveBaseRamCost); } if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { - throw makeRuntimeRejectMsg(workerScript, "workForCompany() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + throw makeRuntimeRejectMsg(workerScript, "setToCompanyWork() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } - updateDynamicRam("workForCompany", CONSTANTS.ScriptSleeveBaseRamCost); + updateDynamicRam("setToCompanyWork", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { - workerScript.log(`ERROR: sleeve.workForCompany(${sleeveNumber}) failed because it is an invalid sleeve number.`); + workerScript.log(`ERROR: sleeve.setToCompanyWork(${sleeveNumber}) failed because it is an invalid sleeve number.`); return false; } return Player.sleeves[sleeveNumber].workForCompany(Player, companyName); }, - workForFaction : function(sleeveNumber=0, factionName="", workType="") { + setToFactionWork : function(sleeveNumber=0, factionName="", workType="") { if (workerScript.checkingRam) { - return updateStaticRam("workForFaction", CONSTANTS.ScriptSleeveBaseRamCost); + return updateStaticRam("setToFactionWork", CONSTANTS.ScriptSleeveBaseRamCost); } if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { - throw makeRuntimeRejectMsg(workerScript, "workForFaction() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + throw makeRuntimeRejectMsg(workerScript, "setToFactionWork() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } - updateDynamicRam("workForFaction", CONSTANTS.ScriptSleeveBaseRamCost); + updateDynamicRam("setToFactionWork", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { - workerScript.log(`ERROR: sleeve.workForFaction(${sleeveNumber}) failed because it is an invalid sleeve number.`); + workerScript.log(`ERROR: sleeve.setToFactionWork(${sleeveNumber}) failed because it is an invalid sleeve number.`); return false; } return Player.sleeves[sleeveNumber].workForFaction(Player, factionName, workType); }, - workoutAtGym : function(sleeveNumber=0, gymName="", stat="") { + setToGymWorkout : function(sleeveNumber=0, gymName="", stat="") { if (workerScript.checkingRam) { - return updateStaticRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); + return updateStaticRam("setToGymWorkout", CONSTANTS.ScriptSleeveBaseRamCost); } if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { - throw makeRuntimeRejectMsg(workerScript, "workoutAtGym() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); + throw makeRuntimeRejectMsg(workerScript, "setToGymWorkout() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); } - updateDynamicRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); + updateDynamicRam("setToGymWorkout", CONSTANTS.ScriptSleeveBaseRamCost); if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { - workerScript.log(`ERROR: sleeve.workoutAtGym(${sleeveNumber}) failed because it is an invalid sleeve number.`); + workerScript.log(`ERROR: sleeve.setToGymWorkout(${sleeveNumber}) failed because it is an invalid sleeve number.`); return false; } return Player.sleeves[sleeveNumber].workoutAtGym(Player, gymName, stat); }, - getStats : function(sleeveNumber=0) { + getSleeveStats : function(sleeveNumber=0) { if (workerScript.checkingRam) { return updateStaticRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost); } @@ -5007,7 +5007,7 @@ function NetscriptFunctions(workerScript) { city: sl.city, hp: sl.hp, jobs: Object.keys(Player.jobs), // technically sleeves have the same jobs as the player. - jobTitle: Object.values(Player.jobs), + jobTitle: Object.values(Player.jobs), maxHp: sl.max_hp, tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), // There's no reason not to give that infomation here as well. Worst case scenario it isn't used. diff --git a/src/ScriptEditor/AceNetscriptMode.js b/src/ScriptEditor/AceNetscriptMode.js index 70d7de56f..18ca0030a 100644 --- a/src/ScriptEditor/AceNetscriptMode.js +++ b/src/ScriptEditor/AceNetscriptMode.js @@ -128,9 +128,9 @@ let NetscriptFunctions = "getNumTriesRemaining|" + // Sleeve API - "sleeve|getNumSleeves|shockRecovery|synchronize|commitCrime|" + - "takeUniversityCourse|travel|workForCompany|workForFaction|workoutAtGym|" + - "getStats|getTask|getInformation"; + "sleeve|getNumSleeves|setToShockRecovery|setToSynchronize|setToCommitCrime|" + + "setToUniversityCourse|travel|setToCompanyWork|setToFactionWork|setToGymWorkout|" + + "getSleeveStats|getTask|getInformation"; var NetscriptHighlightRules = function(options) { var keywordMapper = this.createKeywordMapper({ diff --git a/src/ScriptEditor/CodeMirrorNetscriptMode.js b/src/ScriptEditor/CodeMirrorNetscriptMode.js index 2baf88d91..bf6f2790b 100644 --- a/src/ScriptEditor/CodeMirrorNetscriptMode.js +++ b/src/ScriptEditor/CodeMirrorNetscriptMode.js @@ -233,7 +233,7 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) { "switchCity": atom, "getStamina": atom, "joinBladeburnerFaction": atom, - "getBonusTime": atom, + // Repeat of above "getBonusTime": atom, // Netscript Coding Contract API "codingcontract": atom, @@ -242,6 +242,21 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) { "getData": atom, "getDescription": atom, "getNumTriesRemaining": atom, + + // Sleeve API + "sleeve": atom, + "getNumSleeves": atom, + "setToShockRecovery": atom, + "setToSynchronize": atom, + "setToCommitCrime": atom, + "setToUniversityCourse": atom, + "travel": atom, + "setToCompanyWork": atom, + "setToFactionWork": atom, + "setToGymWorkout": atom, + "getSleeveStats": atom, + "getTask": atom, + "getInformation": atom, }; }(); From d75ff5d95b42a9f263912c3f3e9ac83df7eed0c9 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Mon, 18 Mar 2019 01:15:44 -0700 Subject: [PATCH 28/34] Fixed bug with Corporation bribing. Hoisted isRelevantMaterial() function in IndustryWarehouse up to a class method --- .../ui/CorporationUIEventHandler.js | 5 +- src/Corporation/ui/IndustryWarehouse.jsx | 70 +++++++++---------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js index 713ad825f..d6735d208 100644 --- a/src/Corporation/ui/CorporationUIEventHandler.js +++ b/src/Corporation/ui/CorporationUIEventHandler.js @@ -10,7 +10,8 @@ import { Corporation, OfficeInitialSize, SellSharesCooldown, WarehouseInitialCost, - WarehouseInitialSize } from "../Corporation"; + WarehouseInitialSize, + BribeToRepRatio } from "../Corporation"; import { Industries, IndustryStartingCosts, @@ -23,6 +24,7 @@ import { Product } from "../Product"; import { Player } from "../../Player"; +import { Factions } from "../../Faction/Factions"; import { Cities } from "../../Locations/Cities"; import { numeralWrapper } from "../../ui/numeralFormat"; @@ -146,6 +148,7 @@ export class CorporationEventHandler { } }); const cancelButton = createPopupCloseButton(popupId, { + class: "std-button", display: "inline-block", innerText: "Cancel", }) diff --git a/src/Corporation/ui/IndustryWarehouse.jsx b/src/Corporation/ui/IndustryWarehouse.jsx index 8b69638f1..acb02ebd8 100644 --- a/src/Corporation/ui/IndustryWarehouse.jsx +++ b/src/Corporation/ui/IndustryWarehouse.jsx @@ -76,7 +76,7 @@ function ProductComponent(props) { if (!product.fin) { if (hasUpgradeDashboard) { return ( -
+

Designing {product.name}...


{numeralWrapper.format(product.prog, "0.00")}% complete


@@ -102,7 +102,7 @@ function ProductComponent(props) { ) } else { return ( -
+

Designing {product.name}...


{numeralWrapper.format(product.prog, "0.00")}% complete

@@ -111,7 +111,7 @@ function ProductComponent(props) { } return ( -
+

{product.name}: {numeralWrapper.format(product.data[city][0], nfB)} ({numeralWrapper.format(totalGain, nfB)}/s) @@ -255,7 +255,7 @@ function MaterialComponent(props) { const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division); return ( -

+

{mat.name}: {numeralWrapper.format(mat.qty, nfB)} ({numeralWrapper.format(totalGain, nfB)}/s) @@ -331,6 +331,19 @@ function MaterialComponent(props) { } export class IndustryWarehouse extends BaseReactComponent { + // Returns a boolean indicating whether the given material is relevant for the + // current industry. + isRelevantMaterial(matName, division) { + // Materials that affect Production multiplier + const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"]; + + if (Object.keys(division.reqMats).includes(matName)) { return true; } + if (division.prodMats.includes(matName)) { return true; } + if (prodMultiplierMats.includes(matName)) { return true; } + + return false; + } + renderWarehouseUI() { const corp = this.corp(); const division = this.routing().currentDivision; // Validated in render() @@ -418,34 +431,20 @@ export class IndustryWarehouse extends BaseReactComponent { corp.rerender(); } - // Materials that affect Production multiplier - const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"]; - - // Returns a boolean indicating whether the given material is relevant for the - // current industry. - function isRelevantMaterial(matName) { - if (Object.keys(division.reqMats).includes(matName)) { return true; } - if (division.prodMats.includes(matName)) { return true; } - if (prodMultiplierMats.includes(matName)) { return true; } - - return false; - } - // Create React components for materials const mats = []; for (const matName in warehouse.materials) { if (warehouse.materials[matName] instanceof Material) { // Only create UI for materials that are relevant for the industry - if (isRelevantMaterial(matName)) { - mats.push(MaterialComponent({ - city: this.props.currentCity, - corp: corp, - division: division, - eventHandler: this.eventHandler(), - key: matName, - mat: warehouse.materials[matName], - warehouse: warehouse, - })); + if (this.isRelevantMaterial(matName, division)) { + mats.push(); } } } @@ -455,15 +454,14 @@ export class IndustryWarehouse extends BaseReactComponent { if (division.makesProducts && Object.keys(division.products).length > 0) { for (const productName in division.products) { if (division.products[productName] instanceof Product) { - products.push(ProductComponent({ - city: this.props.currentCity, - corp: corp, - division: division, - eventHandler: this.eventHandler(), - key: productName, - product: division.products[productName], - warehouse: warehouse, - })); + products.push(); } } } From 1f4f6bd179d4bc05354240d807f5d9b638b37918 Mon Sep 17 00:00:00 2001 From: acogdev Date: Tue, 19 Mar 2019 09:52:28 -0600 Subject: [PATCH 29/34] Update purchaseServer docs to account for BitNode 10 BitNode 10 reduces the maximum RAM a purchased server can have so encouraging the use of getPurchasedServerMaxRam() instead of a hardcoded value of 2 ^ 20 --- doc/source/netscript/basicfunctions/purchaseServer.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/netscript/basicfunctions/purchaseServer.rst b/doc/source/netscript/basicfunctions/purchaseServer.rst index caa316550..250077ef6 100644 --- a/doc/source/netscript/basicfunctions/purchaseServer.rst +++ b/doc/source/netscript/basicfunctions/purchaseServer.rst @@ -4,7 +4,7 @@ purchaseServer() Netscript Function .. js:function:: purchaseServer(hostname, ram) :param string hostname: Hostname of the purchased server - :param number ram: Amount of RAM of the purchased server. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of 1048576 (2^20) + :param number ram: Amount of RAM of the purchased server. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of getPurchasedServerMaxRam() :RAM cost: 2.25 GB Purchased a server with the specified hostname and amount of RAM. From bedd615c0e8fab5051a8b59454a5535c0e369bbc Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 18 Mar 2019 16:30:37 -0400 Subject: [PATCH 30/34] Making the QLink almost worth getting. --- src/Augmentation/AugmentationHelpers.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Augmentation/AugmentationHelpers.js b/src/Augmentation/AugmentationHelpers.js index af405e93e..6606635b9 100644 --- a/src/Augmentation/AugmentationHelpers.js +++ b/src/Augmentation/AugmentationHelpers.js @@ -1218,12 +1218,14 @@ function initAugmentations() { "quantum supercomputer, allowing you to access and use its incredible " + "computing power.

" + "This augmentation:
" + - "Increases the player's hacking speed by 10%.
" + - "Increases the player's chance of successfully performing a hack by 30%.
" + - "Increases the amount of money the player gains from hacking by 100%.", - hacking_speed_mult: 1.1, - hacking_chance_mult: 1.3, - hacking_money_mult: 2, + "Increases the player's hacking skill by 100%.
" + + "Increases the player's hacking speed by 50%.
" + + "Increases the player's chance of successfully performing a hack by 150%.
" + + "Increases the amount of money the player gains from hacking by 500%.", + hacking_speed_mult: 1.5, + hacking_chance_mult: 2.5, + hacking_money_mult: 6, + hacking_mult: 2, }); QLink.addToFactions(["Illuminati"]); if (augmentationExists(AugmentationNames.QLink)) { From 0df437ef359285fd9e5102fc1e624b4275cb7516 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 18 Mar 2019 20:15:21 -0400 Subject: [PATCH 31/34] Update AugmentationHelpers.js --- src/Augmentation/AugmentationHelpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Augmentation/AugmentationHelpers.js b/src/Augmentation/AugmentationHelpers.js index 6606635b9..2d056969c 100644 --- a/src/Augmentation/AugmentationHelpers.js +++ b/src/Augmentation/AugmentationHelpers.js @@ -1213,7 +1213,7 @@ function initAugmentations() { //Illuminati var QLink = new Augmentation({ - name:AugmentationNames.QLink, repCost:750e3, moneyCost:1300e6, + name:AugmentationNames.QLink, repCost:750e3, moneyCost:10e12, info:"A brain implant that wirelessly connects you to the Illuminati's " + "quantum supercomputer, allowing you to access and use its incredible " + "computing power.

" + From dfd7aa2d2e604e46b7cb3e77e680fab523403fef Mon Sep 17 00:00:00 2001 From: danielyxie Date: Tue, 19 Mar 2019 16:38:49 -0700 Subject: [PATCH 32/34] Minor fixes to new Sleeve API --- src/BitNode/BitNode.ts | 1 + src/Constants.ts | 3 ++- .../ui/CorporationUIEventHandler.js | 2 +- src/NetscriptFunctions.js | 22 ++++++++++++++++++- src/PersonObjects/Sleeve/Sleeve.ts | 7 +++--- src/Player.js | 6 ++++- 6 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/BitNode/BitNode.ts b/src/BitNode/BitNode.ts index 7ac23b188..8888e8e7c 100644 --- a/src/BitNode/BitNode.ts +++ b/src/BitNode/BitNode.ts @@ -366,6 +366,7 @@ export function initBitNodeMultipliers(p: IPlayer) { BitNodeMultipliers.PurchasedServerCost = 5; BitNodeMultipliers.PurchasedServerLimit = 0.6; BitNodeMultipliers.PurchasedServerMaxRam = 0.5; + BitNodeMultipliers.BladeburnerRank = 0.8; break; case 11: //The Big Crash BitNodeMultipliers.ServerMaxMoney = 0.1; diff --git a/src/Constants.ts b/src/Constants.ts index 916ffce55..5b3a5cdf9 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -303,7 +303,8 @@ export let CONSTANTS: IMap = { ** Bug Fix: An industry's products are now properly separated between different cities * Added a Netscript API for Duplicate Sleeves (by hydroflame) - * Modified BitNode-3's BN multipliers to make it slightly harder + * Modified the multipliers of BitNode-3 and BitNode-8 to make them slightly harder + * After installing Augmentations, Duplicate Sleeves will now default to Synchronize if their Shock is 0 * Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina * Bug Fix: The cost of purchasing Augmentations for Duplicate Sleeves no longer scales with how many Augs you've purchased for yourself ` diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js index d6735d208..1f6edfb2a 100644 --- a/src/Corporation/ui/CorporationUIEventHandler.js +++ b/src/Corporation/ui/CorporationUIEventHandler.js @@ -348,7 +348,7 @@ export class CorporationEventHandler { clickListener:()=>{ mat.exp.splice(i, 1); //Remove export object removeElementById(popupId); - createExportPopup(); + createExportMaterialPopup(mat); } })); })(i, mat, currExports); diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 74133fdf3..6d246a1d2 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -73,7 +73,7 @@ import {WorkerScript, workerScripts, import {makeRuntimeRejectMsg, netscriptDelay, runScriptFromScript} from "./NetscriptEvaluator"; import {NetscriptPort} from "./NetscriptPort"; -import {SleeveTaskType} from "./PersonObjects/Sleeve/SleeveTaskTypesEnum" +import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum" import {Page, routing} from "./ui/navigationTracking"; import {numeralWrapper} from "./ui/numeralFormat"; @@ -4910,6 +4910,16 @@ function NetscriptFunctions(workerScript) { return false; } + // Cannot work at the same company that another sleeve is working at + for (let i = 0; i < Player.sleeves.length; ++i) { + if (i === sleeveNumber) { continue; } + const other = Player.sleeves[i]; + if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) { + workerScript.log(`ERROR: sleeve.setToCompanyWork() failed for Sleeve ${sleeveNumber} because Sleeve ${i} is doing the same task`); + return false; + } + } + return Player.sleeves[sleeveNumber].workForCompany(Player, companyName); }, setToFactionWork : function(sleeveNumber=0, factionName="", workType="") { @@ -4925,6 +4935,16 @@ function NetscriptFunctions(workerScript) { return false; } + // Cannot work at the same faction that another sleeve is working at + for (let i = 0; i < Player.sleeves.length; ++i) { + if (i === sleeveNumber) { continue; } + const other = Player.sleeves[i]; + if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) { + workerScript.log(`ERROR: sleeve.setToFactionWork() failed for Sleeve ${sleeveNumber} because Sleeve ${i} is doing the same task`); + return false; + } + } + return Player.sleeves[sleeveNumber].workForFaction(Player, factionName, workType); }, setToGymWorkout : function(sleeveNumber=0, gymName="", stat="") { diff --git a/src/PersonObjects/Sleeve/Sleeve.ts b/src/PersonObjects/Sleeve/Sleeve.ts index 8e644e2af..6f5a27c98 100644 --- a/src/PersonObjects/Sleeve/Sleeve.ts +++ b/src/PersonObjects/Sleeve/Sleeve.ts @@ -614,7 +614,6 @@ export class Sleeve extends Person { */ travel(p: IPlayer, newCity: string): boolean { if (Cities[newCity] == null) { - console.error(`Invalid city ${newCity} passed into Sleeve.travel()`); return false; } @@ -641,8 +640,8 @@ export class Sleeve extends Person { const company: Company | null = Companies[companyName]; const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]]; - if (company == null) { throw new Error(`Invalid company name specified in Sleeve.workForCompany(): ${companyName}`); } - if (companyPosition == null) { throw new Error(`Invalid CompanyPosition data in Sleeve.workForCompany(): ${companyName}`); } + if (company == null) { return false; } + if (companyPosition == null) { return false; } this.gainRatesForTask.money = companyPosition.baseSalary * company.salaryMultiplier * this.work_money_mult * @@ -684,8 +683,8 @@ export class Sleeve extends Person { * Returns boolean indicating success */ workForFaction(p: IPlayer, factionName: string, workType: string): boolean { + if (factionName === "") { return false; } if (!(Factions[factionName] instanceof Faction) || !p.factions.includes(factionName)) { - throw new Error(`Invalid Faction specified for Sleeve.workForFaction(): ${factionName}`); return false; } diff --git a/src/Player.js b/src/Player.js index ca0c5c0ec..01903d82c 100644 --- a/src/Player.js +++ b/src/Player.js @@ -283,7 +283,11 @@ PlayerObject.prototype.prestigeAugmentation = function() { for (let i = 0; i < this.sleeves.length; ++i) { if (this.sleeves[i] instanceof Sleeve) { - this.sleeves[i].shockRecovery(this); + if (this.sleeves[i].shock >= 100) { + this.sleeves[i].synchronize(this); + } else { + this.sleeves[i].shockRecovery(this); + } } } From 4bdb34bc7b51c48c011c9b180ead02beb48addff Mon Sep 17 00:00:00 2001 From: danielyxie Date: Tue, 19 Mar 2019 17:53:46 -0700 Subject: [PATCH 33/34] numCycleForGrowth() function now accoutns for BitNode growth multiplier. Cleaned up docuemntation --- doc/source/netscript/basicfunctions/enableLog.rst | 2 +- doc/source/netscript/basicfunctions/purchaseServer.rst | 2 +- .../netscript/singularityfunctions/workForCompany.rst | 6 +++++- src/Augmentation/AugmentationHelpers.js | 6 +++--- src/Constants.ts | 3 ++- src/Server/ServerHelpers.ts | 5 +++-- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/doc/source/netscript/basicfunctions/enableLog.rst b/doc/source/netscript/basicfunctions/enableLog.rst index 46b445d53..691b9f133 100644 --- a/doc/source/netscript/basicfunctions/enableLog.rst +++ b/doc/source/netscript/basicfunctions/enableLog.rst @@ -1,5 +1,5 @@ enableLog() Netscript Function -============================= +============================== .. js:function:: enableLog(fn) diff --git a/doc/source/netscript/basicfunctions/purchaseServer.rst b/doc/source/netscript/basicfunctions/purchaseServer.rst index 250077ef6..91052c33b 100644 --- a/doc/source/netscript/basicfunctions/purchaseServer.rst +++ b/doc/source/netscript/basicfunctions/purchaseServer.rst @@ -4,7 +4,7 @@ purchaseServer() Netscript Function .. js:function:: purchaseServer(hostname, ram) :param string hostname: Hostname of the purchased server - :param number ram: Amount of RAM of the purchased server. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of getPurchasedServerMaxRam() + :param number ram: Amount of RAM of the purchased server. Must be a power of 2. Maximum value of :js:func:`getPurchasedServerMaxRam` :RAM cost: 2.25 GB Purchased a server with the specified hostname and amount of RAM. diff --git a/doc/source/netscript/singularityfunctions/workForCompany.rst b/doc/source/netscript/singularityfunctions/workForCompany.rst index fde4d11d7..1c660a26c 100644 --- a/doc/source/netscript/singularityfunctions/workForCompany.rst +++ b/doc/source/netscript/singularityfunctions/workForCompany.rst @@ -1,7 +1,11 @@ workForCompany() Netscript Function =================================== -.. js:function:: workForCompany() +.. js:function:: workForCompany(companyName=lastCompany) + + :param string companyName: Name of company to work for. Must be an exact match. + Optional. If not specified, this argument defaults to + the last job that you worked If you are not in BitNode-4, then you must have Level 2 of Source-File 4 in order to use this function. diff --git a/src/Augmentation/AugmentationHelpers.js b/src/Augmentation/AugmentationHelpers.js index 2d056969c..61eb510e6 100644 --- a/src/Augmentation/AugmentationHelpers.js +++ b/src/Augmentation/AugmentationHelpers.js @@ -1213,19 +1213,19 @@ function initAugmentations() { //Illuminati var QLink = new Augmentation({ - name:AugmentationNames.QLink, repCost:750e3, moneyCost:10e12, + name:AugmentationNames.QLink, repCost:750e3, moneyCost:5e12, info:"A brain implant that wirelessly connects you to the Illuminati's " + "quantum supercomputer, allowing you to access and use its incredible " + "computing power.

" + "This augmentation:
" + - "Increases the player's hacking skill by 100%.
" + + "Increases the player's hacking skill by 50%.
" + "Increases the player's hacking speed by 50%.
" + "Increases the player's chance of successfully performing a hack by 150%.
" + "Increases the amount of money the player gains from hacking by 500%.", hacking_speed_mult: 1.5, hacking_chance_mult: 2.5, hacking_money_mult: 6, - hacking_mult: 2, + hacking_mult: 1.5, }); QLink.addToFactions(["Illuminati"]); if (augmentationExists(AugmentationNames.QLink)) { diff --git a/src/Constants.ts b/src/Constants.ts index 5b3a5cdf9..eb639ad8e 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -302,9 +302,10 @@ export let CONSTANTS: IMap = { ** Training employees is now 3x more effective ** Bug Fix: An industry's products are now properly separated between different cities + * The QLink Augemntation is now significantly stronger (by hydroflame) * Added a Netscript API for Duplicate Sleeves (by hydroflame) * Modified the multipliers of BitNode-3 and BitNode-8 to make them slightly harder - * After installing Augmentations, Duplicate Sleeves will now default to Synchronize if their Shock is 0 + * After installing Augmentations, Duplicate Sleeves will now default to Synchronize if their Shock is 0 * Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina * Bug Fix: The cost of purchasing Augmentations for Duplicate Sleeves no longer scales with how many Augs you've purchased for yourself ` diff --git a/src/Server/ServerHelpers.ts b/src/Server/ServerHelpers.ts index e443deb0c..cd173fd03 100644 --- a/src/Server/ServerHelpers.ts +++ b/src/Server/ServerHelpers.ts @@ -18,7 +18,8 @@ export function numCycleForGrowth(server: Server, growth: number, p: IPlayer) { const serverGrowthPercentage = server.serverGrowth / 100; - const cycles = Math.log(growth)/(Math.log(ajdGrowthRate) * p.hacking_grow_mult * serverGrowthPercentage); + const cycles = Math.log(growth)/(Math.log(ajdGrowthRate) * p.hacking_grow_mult * serverGrowthPercentage * BitNodeMultipliers.ServerGrowthRate); + return cycles; } @@ -96,7 +97,7 @@ export function GetServerByHostname(hostname: string): Server | null { } } } - + return null; } From 2ba7ac82eb9b4d6bcbf7be42ebddf51322733f5a Mon Sep 17 00:00:00 2001 From: danielyxie Date: Fri, 22 Mar 2019 22:11:09 -0700 Subject: [PATCH 34/34] Updated to v0.45.0 production build --- dist/engine.bundle.js | 2 +- dist/engine.css | 41 +++++-- dist/vendor.bundle.js | 152 ++++++++++++++---------- doc/source/changelog.rst | 29 +++++ index.html | 30 +---- src/Augmentation/AugmentationHelpers.js | 12 +- src/Constants.ts | 5 +- 7 files changed, 163 insertions(+), 108 deletions(-) diff --git a/dist/engine.bundle.js b/dist/engine.bundle.js index eeac59b55..fad9493dd 100644 --- a/dist/engine.bundle.js +++ b/dist/engine.bundle.js @@ -1,2 +1,2 @@ -!function(e){function t(t){for(var i,o,s=t[0],l=t[1],c=t[2],p=0,m=[];p0?this.intelligence=Math.floor(this.calculateSkill(this.intelligence_exp)):this.intelligence=0;var e=this.hp/this.max_hp;this.max_hp=Math.floor(10+this.defense/10),q.hp=Math.round(this.max_hp*e)},G.prototype.resetMultipliers=function(){this.hacking_chance_mult=1,this.hacking_speed_mult=1,this.hacking_money_mult=1,this.hacking_grow_mult=1,this.hacking_mult=1,this.strength_mult=1,this.defense_mult=1,this.dexterity_mult=1,this.agility_mult=1,this.charisma_mult=1,this.hacking_exp_mult=1,this.strength_exp_mult=1,this.defense_exp_mult=1,this.dexterity_exp_mult=1,this.agility_exp_mult=1,this.charisma_exp_mult=1,this.company_rep_mult=1,this.faction_rep_mult=1,this.crime_money_mult=1,this.crime_success_mult=1,this.hacknet_node_money_mult=1,this.hacknet_node_purchase_cost_mult=1,this.hacknet_node_ram_cost_mult=1,this.hacknet_node_core_cost_mult=1,this.hacknet_node_level_cost_mult=1,this.work_money_mult=1,this.bladeburner_max_stamina_mult=1,this.bladeburner_stamina_gain_mult=1,this.bladeburner_analysis_mult=1,this.bladeburner_success_chance_mult=1},G.prototype.hasProgram=function(e){var t=q.getHomeComputer();if(null==t)return!1;for(var n=0;n0)&&(this.intelligence_exp+=e)},G.prototype.queryStatFromString=function(e){const t=e.toLowerCase();return t.includes("hack")?q.hacking_skill:t.includes("str")?q.strength:t.includes("def")?q.defense:t.includes("dex")?q.dexterity:t.includes("agi")?q.agility:t.includes("cha")?q.charisma:t.includes("int")?q.intelligence:void 0},G.prototype.resetWorkStatus=function(){this.workHackExpGainRate=0,this.workStrExpGainRate=0,this.workDefExpGainRate=0,this.workDexExpGainRate=0,this.workAgiExpGainRate=0,this.workChaExpGainRate=0,this.workRepGainRate=0,this.workMoneyGainRate=0,this.workMoneyLossRate=0,this.workHackExpGained=0,this.workStrExpGained=0,this.workDefExpGained=0,this.workDexExpGained=0,this.workAgiExpGained=0,this.workChaExpGained=0,this.workRepGained=0,this.workMoneyGained=0,this.timeWorked=0,this.timeWorkedCreateProgram=0,this.currentWorkFactionName="",this.currentWorkFactionDescription="",this.createProgramName="",this.className="",document.getElementById("work-in-progress-text").innerHTML=""},G.prototype.processWorkEarnings=function(e=1){const t=this.workHackExpGainRate*e,n=this.workStrExpGainRate*e,i=this.workDefExpGainRate*e,a=this.workDexExpGainRate*e,r=this.workAgiExpGainRate*e,o=this.workChaExpGainRate*e,s=(this.workMoneyGainRate-this.workMoneyLossRate)*e;this.gainHackingExp(t),this.gainStrengthExp(n),this.gainDefenseExp(i),this.gainDexterityExp(a),this.gainAgilityExp(r),this.gainCharismaExp(o),this.gainMoney(s),this.recordMoneySource(s,"work"),this.workHackExpGained+=t,this.workStrExpGained+=n,this.workDefExpGained+=i,this.workDexExpGained+=a,this.workAgiExpGained+=r,this.workChaExpGained+=o,this.workRepGained+=this.workRepGainRate*e,this.workMoneyGained+=this.workMoneyGainRate*e,this.workMoneyGained-=this.workMoneyLossRate*e},G.prototype.startWork=function(e){this.resetWorkStatus(),this.isWorking=!0,this.companyName=e,this.workType=_.CONSTANTS.WorkTypeCompany,this.workHackExpGainRate=this.getWorkHackExpGain(),this.workStrExpGainRate=this.getWorkStrExpGain(),this.workDefExpGainRate=this.getWorkDefExpGain(),this.workDexExpGainRate=this.getWorkDexExpGain(),this.workAgiExpGainRate=this.getWorkAgiExpGain(),this.workChaExpGainRate=this.getWorkChaExpGain(),this.workRepGainRate=this.getWorkRepGain(),this.workMoneyGainRate=this.getWorkMoneyGain(),this.timeNeededToCompleteWork=_.CONSTANTS.MillisecondsPer8Hours;var t=Object(j.clearEventListeners)("work-in-progress-cancel-button");t.innerHTML="Cancel Work",t.addEventListener("click",function(){return q.finishWork(!0),!1}),v.Engine.loadWorkInProgressContent()},G.prototype.work=function(e){var t=!1;if(this.timeWorked+v.Engine._idleSpeed*e>=_.CONSTANTS.MillisecondsPer8Hours&&(t=!0,e=Math.round((_.CONSTANTS.MillisecondsPer8Hours-this.timeWorked)/v.Engine._idleSpeed)),this.timeWorked+=v.Engine._idleSpeed*e,this.workRepGainRate=this.getWorkRepGain(),this.processWorkEarnings(e),t||this.timeWorked>=_.CONSTANTS.MillisecondsPer8Hours)return this.finishWork(!1);var n=p.Companies[this.companyName],i="0";null!=n&&n instanceof u.Company?i=n.playerReputation:console.error(`Could not find Company: ${this.companyName}`);const a=this.jobs[this.companyName];document.getElementById("work-in-progress-text").innerHTML="You are currently working as a "+a+" at "+this.companyName+" (Current Company Reputation: "+L.numeralWrapper.format(i,"0,0")+")

You have been working for "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+"

You have earned:

$"+L.numeralWrapper.format(this.workMoneyGained,"0,0.00")+" ($"+L.numeralWrapper.format(this.workMoneyGainRate*H,"0,0.00")+" / sec)

"+L.numeralWrapper.format(this.workRepGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workRepGainRate*H,"0,0.0000")+" / sec) reputation for this company

"+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workHackExpGainRate*H,"0,0.0000")+" / sec) hacking exp

"+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workStrExpGainRate*H,"0,0.0000")+" / sec) strength exp
"+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workDefExpGainRate*H,"0,0.0000")+" / sec) defense exp
"+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workDexExpGainRate*H,"0,0.0000")+" / sec) dexterity exp
"+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workAgiExpGainRate*H,"0,0.0000")+" / sec) agility exp

"+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workChaExpGainRate*H,"0,0.0000")+" / sec) charisma exp

You will automatically finish after working for 8 hours. You can cancel earlier if you wish, but you will only gain half of the reputation you've earned so far."},G.prototype.finishWork=function(e,t=!1){e&&(this.workRepGained/=2),p.Companies[this.companyName].playerReputation+=this.workRepGained,this.updateSkillLevels();var n="You earned a total of:
$"+L.numeralWrapper.format(this.workMoneyGained,"0,0.00")+"
"+L.numeralWrapper.format(this.workRepGained,"0,0.0000")+" reputation for the company
"+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hacking exp
"+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" strength exp
"+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" defense exp
"+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dexterity exp
"+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agility exp
"+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" charisma exp
";if(n=e?"You worked a short shift of "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+"

Since you cancelled your work early, you only gained half of the reputation you earned.

"+n:"You worked a full shift of 8 hours!

"+n,t||Object(B.dialogBoxCreate)(n),document.getElementById("mainmenu-container").style.visibility="visible",this.isWorking=!1,v.Engine.loadLocationContent(),t){var i="You worked a short shift of "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+" and earned $"+L.numeralWrapper.format(this.workMoneyGained,"0,0.00")+", "+L.numeralWrapper.format(this.workRepGained,"0,0.0000")+" reputation, "+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hacking exp, "+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" strength exp, "+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" defense exp, "+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dexterity exp, "+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agility exp, and "+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" charisma exp.";return this.resetWorkStatus(),i}this.resetWorkStatus()},G.prototype.startWorkPartTime=function(e){this.resetWorkStatus(),this.isWorking=!0,this.companyName=e,this.workType=_.CONSTANTS.WorkTypeCompanyPartTime,this.workHackExpGainRate=this.getWorkHackExpGain(),this.workStrExpGainRate=this.getWorkStrExpGain(),this.workDefExpGainRate=this.getWorkDefExpGain(),this.workDexExpGainRate=this.getWorkDexExpGain(),this.workAgiExpGainRate=this.getWorkAgiExpGain(),this.workChaExpGainRate=this.getWorkChaExpGain(),this.workRepGainRate=this.getWorkRepGain(),this.workMoneyGainRate=this.getWorkMoneyGain(),this.timeNeededToCompleteWork=_.CONSTANTS.MillisecondsPer8Hours;var t=Object(j.clearEventListeners)("work-in-progress-cancel-button");t.innerHTML="Stop Working",t.addEventListener("click",function(){return q.finishWorkPartTime(),!1}),v.Engine.loadWorkInProgressContent()},G.prototype.workPartTime=function(e){var t=!1;if(this.timeWorked+v.Engine._idleSpeed*e>=_.CONSTANTS.MillisecondsPer8Hours&&(t=!0,e=Math.round((_.CONSTANTS.MillisecondsPer8Hours-this.timeWorked)/v.Engine._idleSpeed)),this.timeWorked+=v.Engine._idleSpeed*e,this.workRepGainRate=this.getWorkRepGain(),this.processWorkEarnings(e),t||this.timeWorked>=_.CONSTANTS.MillisecondsPer8Hours)return this.finishWorkPartTime();var n=p.Companies[this.companyName],i="0";null!=n&&n instanceof u.Company?i=n.playerReputation:console.log("ERROR: Could not find Company: "+this.companyName);const a=this.jobs[this.companyName];document.getElementById("work-in-progress-text").innerHTML="You are currently working as a "+a+" at "+this.companyName+" (Current Company Reputation: "+L.numeralWrapper.format(i,"0,0")+")

You have been working for "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+"

You have earned:

$"+L.numeralWrapper.format(this.workMoneyGained,"0,0.00")+" ($"+L.numeralWrapper.format(this.workMoneyGainRate*H,"0,0.00")+" / sec)

"+L.numeralWrapper.format(this.workRepGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workRepGainRate*H,"0,0.0000")+" / sec) reputation for this company

"+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workHackExpGainRate*H,"0,0.0000")+" / sec) hacking exp

"+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workStrExpGainRate*H,"0,0.0000")+" / sec) strength exp
"+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workDefExpGainRate*H,"0,0.0000")+" / sec) defense exp
"+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workDexExpGainRate*H,"0,0.0000")+" / sec) dexterity exp
"+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workAgiExpGainRate*H,"0,0.0000")+" / sec) agility exp

"+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workChaExpGainRate*H,"0,0.0000")+" / sec) charisma exp

You will automatically finish after working for 8 hours. You can cancel earlier if you wish,
and there will be no penalty because this is a part-time job."},G.prototype.finishWorkPartTime=function(e=!1){p.Companies[this.companyName].playerReputation+=this.workRepGained,this.updateSkillLevels();var t="You earned a total of:
$"+L.numeralWrapper.format(this.workMoneyGained,"0,0.00")+"
"+L.numeralWrapper.format(this.workRepGained,"0,0.0000")+" reputation for the company
"+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hacking exp
"+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" strength exp
"+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" defense exp
"+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dexterity exp
"+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agility exp
"+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" charisma exp
";if(t="You worked for "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+"

"+t,e||Object(B.dialogBoxCreate)(t),document.getElementById("mainmenu-container").style.visibility="visible",this.isWorking=!1,v.Engine.loadLocationContent(),e){var n="You worked for "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+" and earned a total of $"+L.numeralWrapper.format(this.workMoneyGained,"0,0.00")+", "+L.numeralWrapper.format(this.workRepGained,"0,0.0000")+" reputation, "+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hacking exp, "+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" strength exp, "+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" defense exp, "+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dexterity exp, "+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agility exp, and "+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" charisma exp";return this.resetWorkStatus(),n}this.resetWorkStatus()},G.prototype.startFactionWork=function(e){var t=1+e.favor/100;isNaN(t)&&(t=1),this.workRepGainRate*=t,this.workRepGainRate*=s.BitNodeMultipliers.FactionWorkRepGain,this.isWorking=!0,this.workType=_.CONSTANTS.WorkTypeFaction,this.currentWorkFactionName=e.name,this.timeNeededToCompleteWork=_.CONSTANTS.MillisecondsPer20Hours;var n=Object(j.clearEventListeners)("work-in-progress-cancel-button");n.innerHTML="Stop Faction Work",n.addEventListener("click",function(){return q.finishFactionWork(!0),!1}),v.Engine.loadWorkInProgressContent()},G.prototype.startFactionHackWork=function(e){this.resetWorkStatus(),this.workHackExpGainRate=.15*this.hacking_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workRepGainRate=this.workRepGainRate=(this.hacking_skill+this.intelligence)/_.CONSTANTS.MaxSkillLevel*this.faction_rep_mult,this.factionWorkType=_.CONSTANTS.FactionWorkHacking,this.currentWorkFactionDescription="carrying out hacking contracts",this.startFactionWork(e)},G.prototype.startFactionFieldWork=function(e){this.resetWorkStatus(),this.workHackExpGainRate=.1*this.hacking_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workStrExpGainRate=.1*this.strength_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workDefExpGainRate=.1*this.defense_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workDexExpGainRate=.1*this.dexterity_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workAgiExpGainRate=.1*this.agility_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workChaExpGainRate=.1*this.charisma_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workRepGainRate=this.getFactionFieldWorkRepGain(),this.factionWorkType=_.CONSTANTS.FactionWorkField,this.currentWorkFactionDescription="carrying out field missions",this.startFactionWork(e)},G.prototype.startFactionSecurityWork=function(e){this.resetWorkStatus(),this.workHackExpGainRate=.05*this.hacking_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workStrExpGainRate=.15*this.strength_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workDefExpGainRate=.15*this.defense_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workDexExpGainRate=.15*this.dexterity_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workAgiExpGainRate=.15*this.agility_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workChaExpGainRate=0*this.charisma_exp_mult*s.BitNodeMultipliers.FactionWorkExpGain,this.workRepGainRate=this.getFactionSecurityWorkRepGain(),this.factionWorkType=_.CONSTANTS.FactionWorkSecurity,this.currentWorkFactionDescription="performing security detail",this.startFactionWork(e)},G.prototype.workForFaction=function(e){var t=C.Factions[this.currentWorkFactionName];switch(this.factionWorkType){case _.CONSTANTS.FactionWorkHacking:this.workRepGainRate=(this.hacking_skill+this.intelligence)/_.CONSTANTS.MaxSkillLevel*this.faction_rep_mult;break;case _.CONSTANTS.FactionWorkField:this.workRepGainRate=this.getFactionFieldWorkRepGain();break;case _.CONSTANTS.FactionWorkSecurity:this.workRepGainRate=this.getFactionSecurityWorkRepGain()}var n=1+t.favor/100;isNaN(n)&&(n=1),this.workRepGainRate*=n,this.workRepGainRate*=s.BitNodeMultipliers.FactionWorkRepGain;var i=!1;if(this.timeWorked+v.Engine._idleSpeed*e>=_.CONSTANTS.MillisecondsPer20Hours&&(i=!0,e=Math.round((_.CONSTANTS.MillisecondsPer20Hours-this.timeWorked)/v.Engine._idleSpeed)),this.timeWorked+=v.Engine._idleSpeed*e,this.processWorkEarnings(e),i||this.timeWorked>=_.CONSTANTS.MillisecondsPer20Hours)return this.finishFactionWork(!1);document.getElementById("work-in-progress-text").innerHTML="You are currently "+this.currentWorkFactionDescription+" for your faction "+t.name+" (Current Faction Reputation: "+L.numeralWrapper.format(t.playerReputation,"0,0")+").
You have been doing this for "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+"

You have earned:

$"+L.numeralWrapper.format(this.workMoneyGained,"0,0.00")+" ("+L.numeralWrapper.format(this.workMoneyGainRate*H,"0,0.00")+" / sec)

"+L.numeralWrapper.format(this.workRepGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workRepGainRate*H,"0,0.0000")+" / sec) reputation for this faction

"+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workHackExpGainRate*H,"0,0.0000")+" / sec) hacking exp

"+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workStrExpGainRate*H,"0,0.0000")+" / sec) strength exp
"+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workDefExpGainRate*H,"0,0.0000")+" / sec) defense exp
"+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workDexExpGainRate*H,"0,0.0000")+" / sec) dexterity exp
"+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workAgiExpGainRate*H,"0,0.0000")+" / sec) agility exp

"+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workChaExpGainRate*H,"0,0.0000")+" / sec) charisma exp

You will automatically finish after working for 20 hours. You can cancel earlier if you wish.
There is no penalty for cancelling earlier."},G.prototype.finishFactionWork=function(e,t=!1){var n=C.Factions[this.currentWorkFactionName];n.playerReputation+=this.workRepGained,this.updateSkillLevels();var i="You worked for your faction "+n.name+" for a total of "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+"

You earned a total of:
$"+L.numeralWrapper.format(this.workMoneyGained,"0,0.00")+"
"+L.numeralWrapper.format(this.workRepGained,"0,0.0000")+" reputation for the faction
"+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hacking exp
"+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" strength exp
"+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" defense exp
"+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dexterity exp
"+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agility exp
"+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" charisma exp
";if(t||Object(B.dialogBoxCreate)(i),document.getElementById("mainmenu-container").style.visibility="visible",this.isWorking=!1,v.Engine.loadFactionContent(),Object(O.a)(n.name),t){var a="You worked for your faction "+n.name+" for a total of "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+". You earned "+L.numeralWrapper.format(this.workRepGained,"0,0.0000")+" rep, "+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hacking exp, "+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" str exp, "+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" def exp, "+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dex exp, "+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agi exp, and "+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" cha exp.";return this.resetWorkStatus(),a}this.resetWorkStatus()},G.prototype.getWorkMoneyGain=function(){let e=1;const t=p.Companies[this.companyName];M.d&&(e=1+t.favor/100);const n=this.jobs[this.companyName],i=h.CompanyPositions[n];return null==i?(console.error(`Could not find CompanyPosition object for ${n}. Work salary will be 0`),0):i.baseSalary*t.salaryMultiplier*this.work_money_mult*s.BitNodeMultipliers.CompanyWorkMoney*e},G.prototype.getWorkHackExpGain=function(){const e=p.Companies[this.companyName],t=this.jobs[this.companyName],n=h.CompanyPositions[t];return null==e||null==n?(console.error([`Could not find Company object for ${this.companyName}`,`or CompanyPosition object for ${t}.`,"Work hack exp gain will be 0"].join(" ")),0):n.hackingExpGain*e.expMultiplier*this.hacking_exp_mult*s.BitNodeMultipliers.CompanyWorkExpGain},G.prototype.getWorkStrExpGain=function(){const e=p.Companies[this.companyName],t=this.jobs[this.companyName],n=h.CompanyPositions[t];return null==e||null==n?(console.error([`Could not find Company object for ${this.companyName}`,`or CompanyPosition object for ${t}.`,"Work str exp gain will be 0"].join(" ")),0):n.strengthExpGain*e.expMultiplier*this.strength_exp_mult*s.BitNodeMultipliers.CompanyWorkExpGain},G.prototype.getWorkDefExpGain=function(){const e=p.Companies[this.companyName],t=this.jobs[this.companyName],n=h.CompanyPositions[t];return null==e||null==n?(console.error([`Could not find Company object for ${this.companyName}`,`or CompanyPosition object for ${t}.`,"Work def exp gain will be 0"].join(" ")),0):n.defenseExpGain*e.expMultiplier*this.defense_exp_mult*s.BitNodeMultipliers.CompanyWorkExpGain},G.prototype.getWorkDexExpGain=function(){const e=p.Companies[this.companyName],t=this.jobs[this.companyName],n=h.CompanyPositions[t];return null==e||null==n?(console.error([`Could not find Company object for ${this.companyName}`,`or CompanyPosition object for ${t}.`,"Work dex exp gain will be 0"].join(" ")),0):n.dexterityExpGain*e.expMultiplier*this.dexterity_exp_mult*s.BitNodeMultipliers.CompanyWorkExpGain},G.prototype.getWorkAgiExpGain=function(){const e=p.Companies[this.companyName],t=this.jobs[this.companyName],n=h.CompanyPositions[t];return null==e||null==n?(console.error([`Could not find Company object for ${this.companyName}`,`or CompanyPosition object for ${t}.`,"Work agi exp gain will be 0"].join(" ")),0):n.agilityExpGain*e.expMultiplier*this.agility_exp_mult*s.BitNodeMultipliers.CompanyWorkExpGain},G.prototype.getWorkChaExpGain=function(){const e=p.Companies[this.companyName],t=this.jobs[this.companyName],n=h.CompanyPositions[t];return null==e||null==n?(console.error([`Could not find Company object for ${this.companyName}`,`or CompanyPosition object for ${t}.`,"Work cha exp gain will be 0"].join(" ")),0):n.charismaExpGain*e.expMultiplier*this.charisma_exp_mult*s.BitNodeMultipliers.CompanyWorkExpGain},G.prototype.getWorkRepGain=function(){const e=p.Companies[this.companyName],t=this.jobs[this.companyName],n=h.CompanyPositions[t];if(null==e||null==n)return console.error([`Could not find Company object for ${this.companyName}`,`or CompanyPosition object for ${t}.`,"Work rep gain will be 0"].join(" ")),0;var i=n.calculateJobPerformance(this.hacking_skill,this.strength,this.defense,this.dexterity,this.agility,this.charisma);i+=this.intelligence/_.CONSTANTS.MaxSkillLevel;var a=1+e.favor/100;return isNaN(a)&&(a=1),i*this.company_rep_mult*a},G.prototype.getFactionSecurityWorkRepGain=function(){return.9*(this.hacking_skill/_.CONSTANTS.MaxSkillLevel+this.strength/_.CONSTANTS.MaxSkillLevel+this.defense/_.CONSTANTS.MaxSkillLevel+this.dexterity/_.CONSTANTS.MaxSkillLevel+this.agility/_.CONSTANTS.MaxSkillLevel)/4.5*this.faction_rep_mult},G.prototype.getFactionFieldWorkRepGain=function(){return.9*(this.hacking_skill/_.CONSTANTS.MaxSkillLevel+this.strength/_.CONSTANTS.MaxSkillLevel+this.defense/_.CONSTANTS.MaxSkillLevel+this.dexterity/_.CONSTANTS.MaxSkillLevel+this.agility/_.CONSTANTS.MaxSkillLevel+this.charisma/_.CONSTANTS.MaxSkillLevel+this.intelligence/_.CONSTANTS.MaxSkillLevel)/5.5*this.faction_rep_mult},G.prototype.startCreateProgramWork=function(e,t,n){this.resetWorkStatus(),this.isWorking=!0,this.workType=_.CONSTANTS.WorkTypeCreateProgram,this.createProgramReqLvl=n,this.timeNeededToCompleteWork=t;for(var i=0;i=100)break;this.timeWorkedCreateProgram=o/100*this.timeNeededToCompleteWork,this.getHomeComputer().programs.splice(i,1)}}this.createProgramName=e;var s=Object(j.clearEventListeners)("work-in-progress-cancel-button");s.innerHTML="Cancel work on creating program",s.addEventListener("click",function(){return q.finishCreateProgramWork(!0),!1}),v.Engine.loadWorkInProgressContent()},G.prototype.createProgramWork=function(e){var t=this.createProgramReqLvl,n=this.hacking_skill/t;n=1+(n-1)/5,this.timeWorked+=v.Engine._idleSpeed*e,this.timeWorkedCreateProgram+=v.Engine._idleSpeed*e*n;var i=this.createProgramName;this.timeWorkedCreateProgram>=this.timeNeededToCompleteWork&&this.finishCreateProgramWork(!1),document.getElementById("work-in-progress-text").innerHTML="You are currently working on coding "+i+".

You have been working for "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+"

The program is "+(this.timeWorkedCreateProgram/this.timeNeededToCompleteWork*100).toFixed(2)+"% complete.
If you cancel, your work will be saved and you can come back to complete the program later."},G.prototype.finishCreateProgramWork=function(e,t=!1){var n=this.createProgramName;if(!1===e)Object(B.dialogBoxCreate)("You've finished creating "+n+"!
The new program can be found on your home computer."),this.getHomeComputer().programs.push(n);else{var i=n+"-"+(Math.floor(this.timeWorkedCreateProgram/this.timeNeededToCompleteWork*1e4)/100).toString()+"%-INC";this.getHomeComputer().programs.push(i)}e||this.gainIntelligenceExp(this.createProgramReqLvl/_.CONSTANTS.IntelligenceProgramBaseExpGain),document.getElementById("mainmenu-container").style.visibility="visible",this.isWorking=!1,v.Engine.loadTerminalContent(),this.resetWorkStatus()},G.prototype.startClass=function(e,t,n){this.resetWorkStatus(),this.isWorking=!0,this.workType=_.CONSTANTS.WorkTypeStudyClass,this.className=n;var i=1e3/v.Engine._idleSpeed,a=0,r=0,o=0,l=0,c=0,u=0,p=0;switch(n){case _.CONSTANTS.ClassStudyComputerScience:r=.5*t/i;break;case _.CONSTANTS.ClassDataStructures:a=_.CONSTANTS.ClassDataStructuresBaseCost*e/i,r=1*t/i;break;case _.CONSTANTS.ClassNetworks:a=_.CONSTANTS.ClassNetworksBaseCost*e/i,r=2*t/i;break;case _.CONSTANTS.ClassAlgorithms:a=_.CONSTANTS.ClassAlgorithmsBaseCost*e/i,r=4*t/i;break;case _.CONSTANTS.ClassManagement:a=_.CONSTANTS.ClassManagementBaseCost*e/i,p=2*t/i;break;case _.CONSTANTS.ClassLeadership:a=_.CONSTANTS.ClassLeadershipBaseCost*e/i,p=4*t/i;break;case _.CONSTANTS.ClassGymStrength:a=_.CONSTANTS.ClassGymBaseCost*e/i,o=1*t/i;break;case _.CONSTANTS.ClassGymDefense:a=_.CONSTANTS.ClassGymBaseCost*e/i,l=1*t/i;break;case _.CONSTANTS.ClassGymDexterity:a=_.CONSTANTS.ClassGymBaseCost*e/i,c=1*t/i;break;case _.CONSTANTS.ClassGymAgility:a=_.CONSTANTS.ClassGymBaseCost*e/i,u=1*t/i;break;default:throw new Error("ERR: Invalid/unrecognized class name")}this.workMoneyLossRate=a,this.workHackExpGainRate=r*this.hacking_exp_mult*s.BitNodeMultipliers.ClassGymExpGain,this.workStrExpGainRate=o*this.strength_exp_mult*s.BitNodeMultipliers.ClassGymExpGain,this.workDefExpGainRate=l*this.defense_exp_mult*s.BitNodeMultipliers.ClassGymExpGain,this.workDexExpGainRate=c*this.dexterity_exp_mult*s.BitNodeMultipliers.ClassGymExpGain,this.workAgiExpGainRate=u*this.agility_exp_mult*s.BitNodeMultipliers.ClassGymExpGain,this.workChaExpGainRate=p*this.charisma_exp_mult*s.BitNodeMultipliers.ClassGymExpGain;var m=Object(j.clearEventListeners)("work-in-progress-cancel-button");n==_.CONSTANTS.ClassGymStrength||n==_.CONSTANTS.ClassGymDefense||n==_.CONSTANTS.ClassGymDexterity||n==_.CONSTANTS.ClassGymAgility?m.innerHTML="Stop training at gym":m.innerHTML="Stop taking course",m.addEventListener("click",function(){return q.finishClass(),!1}),v.Engine.loadWorkInProgressContent()},G.prototype.takeClass=function(e){this.timeWorked+=v.Engine._idleSpeed*e;var t=this.className;this.processWorkEarnings(e),document.getElementById("work-in-progress-text").innerHTML="You have been "+t+" for "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+"

This has cost you:
$"+L.numeralWrapper.format(this.workMoneyGained,"0,0.00")+" ($"+L.numeralWrapper.format(this.workMoneyLossRate*H,"0,0.00")+" / sec)

You have gained:
"+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workHackExpGainRate*H,"0,0.0000")+" / sec) hacking exp
"+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workStrExpGainRate*H,"0,0.0000")+" / sec) strength exp
"+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workDefExpGainRate*H,"0,0.0000")+" / sec) defense exp
"+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workDexExpGainRate*H,"0,0.0000")+" / sec) dexterity exp
"+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workAgiExpGainRate*H,"0,0.0000")+" / sec) agility exp
"+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" ("+L.numeralWrapper.format(this.workChaExpGainRate*H,"0,0.0000")+" / sec) charisma exp
You may cancel at any time"},G.prototype.finishClass=function(e=!1){if(this.gainIntelligenceExp(_.CONSTANTS.IntelligenceClassBaseExpGain*Math.round(this.timeWorked/1e3)),this.workMoneyGained>0)throw new Error("ERR: Somehow gained money while taking class");this.updateSkillLevels();var t="After "+this.className+" for "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+",
you spent a total of $"+L.numeralWrapper.format(-1*this.workMoneyGained,"0,0.00")+".

You earned a total of:
"+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hacking exp
"+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" strength exp
"+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" defense exp
"+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dexterity exp
"+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agility exp
"+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" charisma exp
";if(e||Object(B.dialogBoxCreate)(t),document.getElementById("mainmenu-container").style.visibility="visible",this.isWorking=!1,v.Engine.loadLocationContent(),e){var n="After "+this.className+" for "+Object(U.convertTimeMsToTimeElapsedString)(this.timeWorked)+", you spent a total of $"+L.numeralWrapper.format(-1*this.workMoneyGained,"0,0.00")+". You earned a total of: "+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hacking exp, "+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" strength exp, "+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" defense exp, "+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dexterity exp, "+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agility exp, and "+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" charisma exp";return this.resetWorkStatus(),n}this.resetWorkStatus()},G.prototype.startCrime=function(e,t,n,i,a,r,o,l,c,u=null){this.crimeType=e,this.resetWorkStatus(),this.isWorking=!0,this.workType=_.CONSTANTS.WorkTypeCrime,u&&u.workerscript&&(this.committingCrimeThruSingFn=!0,this.singFnCrimeWorkerScript=u.workerscript),this.workHackExpGained=t*this.hacking_exp_mult*s.BitNodeMultipliers.CrimeExpGain,this.workStrExpGained=n*this.strength_exp_mult*s.BitNodeMultipliers.CrimeExpGain,this.workDefExpGained=i*this.defense_exp_mult*s.BitNodeMultipliers.CrimeExpGain,this.workDexExpGained=a*this.dexterity_exp_mult*s.BitNodeMultipliers.CrimeExpGain,this.workAgiExpGained=r*this.agility_exp_mult*s.BitNodeMultipliers.CrimeExpGain,this.workChaExpGained=o*this.charisma_exp_mult*s.BitNodeMultipliers.CrimeExpGain,this.workMoneyGained=l*this.crime_money_mult*s.BitNodeMultipliers.CrimeMoney,this.timeNeededToCompleteWork=c;var p=Object(j.clearEventListeners)("work-in-progress-cancel-button");p.innerHTML="Cancel crime",p.addEventListener("click",function(){return q.finishCrime(!0),!1}),v.Engine.loadWorkInProgressContent()},G.prototype.commitCrime=function(e){if(this.timeWorked+=v.Engine._idleSpeed*e,this.timeWorked>=this.timeNeededToCompleteWork)this.finishCrime(!1);else{var t=Math.round(this.timeWorked/this.timeNeededToCompleteWork*100),n=Math.round(t/5);n<0&&(n=0),n>20&&(n=20);var i="["+Array(n+1).join("|")+Array(20-n+1).join(" ")+"]";document.getElementById("work-in-progress-text").innerHTML="You are attempting to "+this.crimeType+".
Time remaining: "+Object(U.convertTimeMsToTimeElapsedString)(this.timeNeededToCompleteWork-this.timeWorked)+"
"+i.replace(/ /g," ")}},G.prototype.finishCrime=function(e){if(!e){if(Object(b.determineCrimeSuccess)(q,this.crimeType)){let e=null;for(const t in E.Crimes)if(E.Crimes[t].type==this.crimeType){e=E.Crimes[t];break}null==e&&(console.log(this.crimeType),Object(B.dialogBoxCreate)("ERR: Unrecognized crime type. This is probably a bug please contact the developer")),q.gainMoney(this.workMoneyGained),q.recordMoneySource(this.workMoneyGained,"crime"),this.karma-=e.karma,this.numPeopleKilled+=e.kills,e.intelligence_exp>0&&this.gainIntelligenceExp(e.intelligence_exp),this.workHackExpGained*=2,this.workStrExpGained*=2,this.workDefExpGained*=2,this.workDexExpGained*=2,this.workAgiExpGained*=2,this.workChaExpGained*=2,this.committingCrimeThruSingFn?null==this.singFnCrimeWorkerScript.disableLogs.ALL&&null==this.singFnCrimeWorkerScript.disableLogs.commitCrime&&this.singFnCrimeWorkerScript.scriptRef.log("Crime successful! Gained "+L.numeralWrapper.format(this.workMoneyGained,"$0.000a")+", "+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hack exp, "+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" str exp, "+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" def exp, "+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dex exp, "+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agi exp, "+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" cha exp."):Object(B.dialogBoxCreate)("Crime successful!

You gained:
$"+L.numeralWrapper.format(this.workMoneyGained,"0,0.00")+"
"+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hacking experience
"+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" strength experience
"+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" defense experience
"+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dexterity experience
"+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agility experience
"+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" charisma experience")}else this.workHackExpGained/=2,this.workStrExpGained/=2,this.workDefExpGained/=2,this.workDexExpGained/=2,this.workAgiExpGained/=2,this.workChaExpGained/=2,this.committingCrimeThruSingFn?null==this.singFnCrimeWorkerScript.disableLogs.ALL&&null==this.singFnCrimeWorkerScript.disableLogs.commitCrime&&this.singFnCrimeWorkerScript.scriptRef.log("Crime failed! Gained "+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hack exp, "+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" str exp, "+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" def exp, "+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dex exp, "+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agi exp, "+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" cha exp."):Object(B.dialogBoxCreate)("Crime failed!

You gained:
"+L.numeralWrapper.format(this.workHackExpGained,"0,0.0000")+" hacking experience
"+L.numeralWrapper.format(this.workStrExpGained,"0,0.0000")+" strength experience
"+L.numeralWrapper.format(this.workDefExpGained,"0,0.0000")+" defense experience
"+L.numeralWrapper.format(this.workDexExpGained,"0,0.0000")+" dexterity experience
"+L.numeralWrapper.format(this.workAgiExpGained,"0,0.0000")+" agility experience
"+L.numeralWrapper.format(this.workChaExpGained,"0,0.0000")+" charisma experience");this.gainHackingExp(this.workHackExpGained),this.gainStrengthExp(this.workStrExpGained),this.gainDefenseExp(this.workDefExpGained),this.gainDexterityExp(this.workDexExpGained),this.gainAgilityExp(this.workAgiExpGained),this.gainCharismaExp(this.workChaExpGained)}this.committingCrimeThruSingFn=!1,this.singFnCrimeWorkerScript=null,document.getElementById("mainmenu-container").style.visibility="visible",this.isWorking=!1,this.resetWorkStatus(),v.Engine.loadLocationContent()},G.prototype.singularityStopWork=function(){if(!this.isWorking)return"";var e;switch(this.workType){case _.CONSTANTS.WorkTypeStudyClass:e=this.finishClass(!0);break;case _.CONSTANTS.WorkTypeCompany:e=this.finishWork(!0,!0);break;case _.CONSTANTS.WorkTypeCompanyPartTime:e=this.finishWorkPartTime(!0);break;case _.CONSTANTS.WorkTypeFaction:e=this.finishFactionWork(!0,!0);break;case _.CONSTANTS.WorkTypeCreateProgram:e=this.finishCreateProgramWork(!0,!0);break;case _.CONSTANTS.WorkTypeCrime:e=this.finishCrime(!0);break;default:return console.log("ERROR: Unrecognized work type"),""}return e},G.prototype.takeDamage=function(e){if("number"==typeof e)return this.hp-=e,this.hp<=0&&(this.hospitalize(),!0);console.warn(`Player.takeDamage() called without a numeric argument: ${e}`)},G.prototype.regenerateHp=function(e){"number"==typeof e?(this.hp+=e,this.hp>this.max_hp&&(this.hp=this.max_hp)):console.warn(`Player.regenerateHp() called without a numeric argument: ${e}`)},G.prototype.hospitalize=function(){!1===w.Settings.SuppressHospitalizationPopup&&Object(B.dialogBoxCreate)("You were in critical condition! You were taken to the hospital where luckily they were able to save your life. You were charged "+L.numeralWrapper.format(this.max_hp*_.CONSTANTS.HospitalCostPerHp,"$0.000a")),this.loseMoney(this.max_hp*_.CONSTANTS.HospitalCostPerHp),this.hp=this.max_hp},G.prototype.applyForJob=function(e,t=!1){let n=null;""!==this.companyName&&(n=p.Companies[this.companyName]);const i=this.jobs[this.companyName],a=p.Companies[this.location];if(!(a instanceof u.Company))return t?"ERROR: Invalid company name: "+this.location+". applyToCompany() failed":void console.error(`Could not find company that matches the location: ${this.location}. Player.applyToCompany() failed`);let r=e;if(!this.isQualified(a,r)){var o=Object(d.getJobRequirementText)(a,r);return!t&&void Object(B.dialogBoxCreate)("Unforunately, you do not qualify for this position
"+o)}for(;;){let e=Object(m.getNextCompanyPosition)(r);if(null==e)break;if(!a.hasPosition(e))break;if(!this.isQualified(a,e))break;r=e}if(null==n||n.name!=a.name||r.name!=i){if(this.companyName=a.name,this.jobs[a.name]=r.name,document.getElementById("world-menu-header").click(),document.getElementById("world-menu-header").click(),t)return!0;Object(B.dialogBoxCreate)("Congratulations! You were offered a new job at "+this.companyName+" as a "+r.name+"!"),v.Engine.loadLocationContent()}else{var s=Object(m.getNextCompanyPosition)(r);if(null==s){if(t)return!1;Object(B.dialogBoxCreate)("You are already at the highest position for your field! No promotion available")}else if(a.hasPosition(s)){if(t)return!1;o=Object(d.getJobRequirementText)(a,s);Object(B.dialogBoxCreate)("Unfortunately, you do not qualify for a promotion
"+o)}else{if(t)return!1;Object(B.dialogBoxCreate)("You are already at the highest position for your field! No promotion available")}}},G.prototype.getNextCompanyPosition=function(e,t){var n=null;if(""!==this.companyName&&(n=p.Companies[this.companyName]),null==n||n.name!=e.name)return t;const i=this.jobs[this.companyName],a=h.CompanyPositions[i];return a.isSoftwareJob()&&t.isSoftwareJob()||a.isITJob()&&t.isITJob()||a.isBusinessJob()&&t.isBusinessJob()||a.isSecurityEngineerJob()&&t.isSecurityEngineerJob()||a.isNetworkEngineerJob()&&t.isNetworkEngineerJob()||a.isSecurityJob()&&t.isSecurityJob()||a.isAgentJob()&&t.isAgentJob()||a.isSoftwareConsultantJob()&&t.isSoftwareConsultantJob()||a.isBusinessConsultantJob()&&t.isBusinessConsultantJob()||a.isPartTimeJob()&&t.isPartTimeJob()?Object(m.getNextCompanyPosition)(a):t},G.prototype.applyForSoftwareJob=function(e=!1){return this.applyForJob(h.CompanyPositions[g.SoftwareCompanyPositions[0]],e)},G.prototype.applyForSoftwareConsultantJob=function(e=!1){return this.applyForJob(h.CompanyPositions[g.SoftwareConsultantCompanyPositions[0]],e)},G.prototype.applyForItJob=function(e=!1){return this.applyForJob(h.CompanyPositions[g.ITCompanyPositions[0]],e)},G.prototype.applyForSecurityEngineerJob=function(e=!1){var t=p.Companies[this.location];return this.isQualified(t,h.CompanyPositions[g.SecurityEngineerCompanyPositions[0]])?this.applyForJob(h.CompanyPositions[g.SecurityEngineerCompanyPositions[0]],e):!e&&void Object(B.dialogBoxCreate)("Unforunately, you do not qualify for this position")},G.prototype.applyForNetworkEngineerJob=function(e=!1){var t=p.Companies[this.location];return this.isQualified(t,h.CompanyPositions[g.NetworkEngineerCompanyPositions[0]])?this.applyForJob(h.CompanyPositions[g.NetworkEngineerCompanyPositions[0]],e):!e&&void Object(B.dialogBoxCreate)("Unforunately, you do not qualify for this position")},G.prototype.applyForBusinessJob=function(e=!1){return this.applyForJob(h.CompanyPositions[g.BusinessCompanyPositions[0]],e)},G.prototype.applyForBusinessConsultantJob=function(e=!1){return this.applyForJob(h.CompanyPositions[g.BusinessConsultantCompanyPositions[0]],e)},G.prototype.applyForSecurityJob=function(e=!1){return this.applyForJob(h.CompanyPositions[g.SecurityCompanyPositions[2]],e)},G.prototype.applyForAgentJob=function(e=!1){var t=p.Companies[this.location];return this.isQualified(t,h.CompanyPositions[g.AgentCompanyPositions[0]])?this.applyForJob(h.CompanyPositions[g.AgentCompanyPositions[0]],e):!e&&void Object(B.dialogBoxCreate)("Unforunately, you do not qualify for this position")},G.prototype.applyForEmployeeJob=function(e=!1){var t=p.Companies[this.location];if(this.isQualified(t,h.CompanyPositions[g.MiscCompanyPositions[1]])){if(this.companyName=t.name,this.jobs[t.name]=g.MiscCompanyPositions[1],document.getElementById("world-menu-header").click(),document.getElementById("world-menu-header").click(),e)return!0;Object(B.dialogBoxCreate)("Congratulations, you are now employed at "+this.companyName),v.Engine.loadLocationContent()}else{if(e)return!1;Object(B.dialogBoxCreate)("Unforunately, you do not qualify for this position")}},G.prototype.applyForPartTimeEmployeeJob=function(e=!1){var t=p.Companies[this.location];if(this.isQualified(t,h.CompanyPositions[g.PartTimeCompanyPositions[1]])){if(this.companyName=t.name,this.jobs[t.name]=g.PartTimeCompanyPositions[1],document.getElementById("world-menu-header").click(),document.getElementById("world-menu-header").click(),e)return!0;Object(B.dialogBoxCreate)("Congratulations, you are now employed part-time at "+this.companyName),v.Engine.loadLocationContent()}else{if(e)return!1;Object(B.dialogBoxCreate)("Unforunately, you do not qualify for this position")}},G.prototype.applyForWaiterJob=function(e=!1){var t=p.Companies[this.location];if(this.isQualified(t,h.CompanyPositions[g.MiscCompanyPositions[0]])){if(this.companyName=t.name,this.jobs[t.name]=g.MiscCompanyPositions[0],document.getElementById("world-menu-header").click(),document.getElementById("world-menu-header").click(),e)return!0;Object(B.dialogBoxCreate)("Congratulations, you are now employed as a waiter at "+this.companyName),v.Engine.loadLocationContent()}else{if(e)return!1;Object(B.dialogBoxCreate)("Unforunately, you do not qualify for this position")}},G.prototype.applyForPartTimeWaiterJob=function(e=!1){var t=p.Companies[this.location];if(this.isQualified(t,h.CompanyPositions[g.PartTimeCompanyPositions[0]])){if(this.companyName=t.name,this.jobs[t.name]=g.PartTimeCompanyPositions[0],document.getElementById("world-menu-header").click(),document.getElementById("world-menu-header").click(),e)return!0;Object(B.dialogBoxCreate)("Congratulations, you are now employed as a part-time waiter at "+this.companyName),v.Engine.loadLocationContent()}else{if(e)return!1;Object(B.dialogBoxCreate)("Unforunately, you do not qualify for this position")}},G.prototype.isQualified=function(e,t){var n=e.jobStatReqOffset,i=t.requiredHacking>0?t.requiredHacking+n:0,a=t.requiredStrength>0?t.requiredStrength+n:0,r=t.requiredDefense>0?t.requiredDefense+n:0,o=t.requiredDexterity>0?t.requiredDexterity+n:0,s=t.requiredDexterity>0?t.requiredDexterity+n:0,l=t.requiredCharisma>0?t.requiredCharisma+n:0;return this.hacking_skill>=i&&this.strength>=a&&this.defense>=r&&this.dexterity>=o&&this.agility>=s&&this.charisma>=l&&e.playerReputation>=t.requiredReputation},G.prototype.reapplyAllAugmentations=function(e=!0){console.log("Re-applying augmentations"),e&&this.resetMultipliers();for(let e=0;et}var r=C.Factions.Illuminati;!r.isBanned&&!r.isMember&&!r.alreadyInvited&&t>=30&&this.money.gte(15e10)&&this.hacking_skill>=1500&&this.strength>=1200&&this.defense>=1200&&this.dexterity>=1200&&this.agility>=1200&&e.push(r);var o=C.Factions.Daedalus;!o.isBanned&&!o.isMember&&!o.alreadyInvited&&t>=Math.round(30*s.BitNodeMultipliers.DaedalusAugsRequirement)&&this.money.gte(1e11)&&(this.hacking_skill>=2500||this.strength>=1500&&this.defense>=1500&&this.dexterity>=1500&&this.agility>=1500)&&e.push(o);var l=C.Factions["The Covenant"];!l.isBanned&&!l.isMember&&!l.alreadyInvited&&t>=20&&this.money.gte(75e9)&&this.hacking_skill>=850&&this.strength>=850&&this.defense>=850&&this.dexterity>=850&&this.agility>=850&&e.push(l);var c=C.Factions.ECorp;c.isBanned||c.isMember||c.alreadyInvited||!a(S.Locations.AevumECorp)||e.push(c);var u=C.Factions.MegaCorp;u.isBanned||u.isMember||u.alreadyInvited||!a(S.Locations.Sector12MegaCorp)||e.push(u);var m=C.Factions["Bachman & Associates"];m.isBanned||m.isMember||m.alreadyInvited||!a(S.Locations.AevumBachmanAndAssociates)||e.push(m);var d=C.Factions["Blade Industries"];d.isBanned||d.isMember||d.alreadyInvited||!a(S.Locations.Sector12BladeIndustries)||e.push(d);var h=C.Factions.NWO;h.isBanned||h.isMember||h.alreadyInvited||!a(S.Locations.VolhavenNWO)||e.push(h);var g=C.Factions["Clarke Incorporated"];g.isBanned||g.isMember||g.alreadyInvited||!a(S.Locations.AevumClarkeIncorporated)||e.push(g);var y=C.Factions["OmniTek Incorporated"];y.isBanned||y.isMember||y.alreadyInvited||!a(S.Locations.VolhavenOmniTekIncorporated)||e.push(y);var f=C.Factions["Four Sigma"];f.isBanned||f.isMember||f.alreadyInvited||!a(S.Locations.Sector12FourSigma)||e.push(f);var b=C.Factions["KuaiGong International"];b.isBanned||b.isMember||b.alreadyInvited||!a(S.Locations.ChongqingKuaiGongInternational)||e.push(b);var E=C.Factions["Fulcrum Secret Technologies"],v=A.b[x.a[x.b.FulcrumSecretTechnologies]];null==v?console.log("ERROR: Could not find Fulcrum Secret Technologies Server"):E.isBanned||E.isMember||E.alreadyInvited||!v.manuallyHacked||!a(S.Locations.AevumFulcrumTechnologies,25e4)||e.push(E);var k=C.Factions.BitRunners,O=this.getHomeComputer(),T=A.b[x.a[x.b.BitRunnersServer]];null==T?console.log("ERROR: Could not find BitRunners Server"):!k.isBanned&&!k.isMember&&T.manuallyHacked&&!k.alreadyInvited&&this.hacking_skill>=500&&O.maxRam>=128&&e.push(k);var M=C.Factions["The Black Hand"],P=A.b[x.a[x.b.TheBlackHandServer]];null==P?console.log("ERROR: Could not find The Black Hand Server"):!M.isBanned&&!M.isMember&&P.manuallyHacked&&!M.alreadyInvited&&this.hacking_skill>=350&&O.maxRam>=64&&e.push(M);var w=C.Factions.NiteSec,R=A.b[x.a[x.b.NiteSecServer]];null==R?console.log("ERROR: Could not find NiteSec Server"):!w.isBanned&&!w.isMember&&R.manuallyHacked&&!w.alreadyInvited&&this.hacking_skill>=200&&O.maxRam>=32&&e.push(w);var N=C.Factions.Chongqing;N.isBanned||N.isMember||N.alreadyInvited||!this.money.gte(2e7)||this.city!=S.Locations.Chongqing||e.push(N);var I=C.Factions["Sector-12"];I.isBanned||I.isMember||I.alreadyInvited||!this.money.gte(15e6)||this.city!=S.Locations.Sector12||e.push(I);var L=C.Factions["New Tokyo"];L.isBanned||L.isMember||L.alreadyInvited||!this.money.gte(2e7)||this.city!=S.Locations.NewTokyo||e.push(L);var D=C.Factions.Aevum;D.isBanned||D.isMember||D.alreadyInvited||!this.money.gte(4e7)||this.city!=S.Locations.Aevum||e.push(D);var B=C.Factions.Ishima;B.isBanned||B.isMember||B.alreadyInvited||!this.money.gte(3e7)||this.city!=S.Locations.Ishima||e.push(B);var j=C.Factions.Volhaven;j.isBanned||j.isMember||j.alreadyInvited||!this.money.gte(5e7)||this.city!=S.Locations.Volhaven||e.push(j);var W=C.Factions["Speakers for the Dead"];!W.isBanned&&!W.isMember&&!W.alreadyInvited&&this.hacking_skill>=100&&this.strength>=300&&this.defense>=300&&this.dexterity>=300&&this.agility>=300&&this.numPeopleKilled>=30&&this.karma<=-45&&!n.includes(S.Locations.Sector12CIA)&&!n.includes(S.Locations.Sector12NSA)&&e.push(W);var F=C.Factions["The Dark Army"];!F.isBanned&&!F.isMember&&!F.alreadyInvited&&this.hacking_skill>=300&&this.strength>=300&&this.defense>=300&&this.dexterity>=300&&this.agility>=300&&this.city==S.Locations.Chongqing&&this.numPeopleKilled>=5&&this.karma<=-45&&!n.includes(S.Locations.Sector12CIA)&&!n.includes(S.Locations.Sector12NSA)&&e.push(F);var U=C.Factions["The Syndicate"];!U.isBanned&&!U.isMember&&!U.alreadyInvited&&this.hacking_skill>=200&&this.strength>=200&&this.defense>=200&&this.dexterity>=200&&this.agility>=200&&(this.city==S.Locations.Aevum||this.city==S.Locations.Sector12)&&this.money.gte(1e7)&&this.karma<=-90&&!n.includes(S.Locations.Sector12CIA)&&!n.includes(S.Locations.Sector12NSA)&&e.push(U);var H=C.Factions.Silhouette;!H.isBanned&&!H.isMember&&!H.alreadyInvited&&(i.includes("Chief Technology Officer")||i.includes("Chief Financial Officer")||i.includes("Chief Executive Officer"))&&this.money.gte(15e6)&&this.karma<=-22&&e.push(H);var G=C.Factions.Tetrads;!G.isBanned&&!G.isMember&&!G.alreadyInvited&&(this.city==S.Locations.Chongqing||this.city==S.Locations.NewTokyo||this.city==S.Locations.Ishima)&&this.strength>=75&&this.defense>=75&&this.dexterity>=75&&this.agility>=75&&this.karma<=-18&&e.push(G);var K=C.Factions["Slum Snakes"];!K.isBanned&&!K.isMember&&!K.alreadyInvited&&this.strength>=30&&this.defense>=30&&this.dexterity>=30&&this.agility>=30&&this.karma<=-9&&this.money.gte(1e6)&&e.push(K);for(var q=C.Factions.Netburners,$=0,Y=0,z=0,V=0;V=80&&$>=8&&Y>=4&&z>=100&&e.push(q);var J=C.Factions["Tian Di Hui"];J.isBanned||J.isMember||J.alreadyInvited||!this.money.gte(1e6)||!(this.hacking_skill>=50)||this.city!=S.Locations.Chongqing&&this.city!=S.Locations.NewTokyo&&this.city!=S.Locations.Ishima||e.push(J);var X=C.Factions.CyberSec,Q=A.b[x.a[x.b.CyberSecServer]];return null==Q?console.log("ERROR: Could not find CyberSec Server"):!X.isBanned&&!X.isMember&&Q.manuallyHacked&&!X.alreadyInvited&&this.hacking_skill>=50&&e.push(X),e},G.prototype.inGang=function(){return null!=this.gang&&void 0!=this.gang&&this.gang instanceof T.b},G.prototype.startGang=function(e,t){this.gang=new T.b(e,t)},G.prototype.hasCorporation=function(){return null!=this.corporation&&this.corporation instanceof y.a},G.prototype.inBladeburner=function(){return null!=this.bladeburner&&this.bladeburner instanceof l.a},G.prototype.setBitNodeNumber=function(e){this.bitNodeN=e},G.prototype.queueAugmentation=function(e){for(const t in this.queuedAugmentations)if(this.queuedAugmentations[t].name==e)return void console.log("tried to queue "+e+" twice, this may be a bug");for(const t in this.augmentations)if(this.augmentations[t].name==e)return void console.log("tried to queue "+e+" but we already have that aug");this.firstAugPurchased=!0,this.queuedAugmentations.push(new r.PlayerOwnedAugmentation(e))},G.prototype.gainCodingContractReward=function(e,t=1){if(null==e||null==e.type||null==e)return"No reward for this contract";switch(e.type){case c.CodingContractRewardType.FactionReputation:if(null==e.name||!(C.Factions[e.name]instanceof k.Faction))return e.type=c.CodingContractRewardType.FactionReputationAll,this.gainCodingContractReward(e);var n=_.CONSTANTS.CodingContractBaseFactionRepGain*t;return C.Factions[e.name].playerReputation+=n,`Gained ${n} faction reputation for ${e.name}`;case c.CodingContractRewardType.FactionReputationAll:const r=_.CONSTANTS.CodingContractBaseFactionRepGain*t,o=["Bladeburners"];var i=this.factions.slice();if(0==(i=i.filter(e=>!o.includes(e))).length)return e.type=c.CodingContractRewardType.Money,this.gainCodingContractReward(e,t);const l=Math.floor(r/i.length);for(const e of i)C.Factions[e]instanceof k.Faction&&(C.Factions[e].playerReputation+=l);return`Gained ${l} reputation for each of the following factions: ${i.toString()}`;case c.CodingContractRewardType.CompanyReputation:if(null==e.name||!(p.Companies[e.name]instanceof u.Company))return e.type=c.CodingContractRewardType.FactionReputationAll,this.gainCodingContractReward(e);n=_.CONSTANTS.CodingContractBaseCompanyRepGain*t;return p.Companies[e.name].playerReputation+=n,`Gained ${n} company reputation for ${e.name}`;case c.CodingContractRewardType.Money:default:var a=_.CONSTANTS.CodingContractBaseMoneyGain*t*s.BitNodeMultipliers.CodingContractMoney;return this.gainMoney(a),this.recordMoneySource(a,"codingcontract"),`Gained ${L.numeralWrapper.format(a,"$0.000a")}`}},G.prototype.toJSON=function(){return Object(F.Generic_toJSON)("PlayerObject",this)},G.fromJSON=function(e){return Object(F.Generic_fromJSON)(G,e.data)},F.Reviver.constructors.PlayerObject=G;let q=new G},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CONSTANTS={Version:"0.44.1",MaxSkillLevel:975,MilliPerCycle:200,CorpFactionRepRequirement:2e5,BaseCostFor1GBOfRamHome:32e3,BaseCostFor1GBOfRamServer:55e3,BaseCostFor1GBOfRamHacknetNode:3e4,TravelCost:2e5,BaseCostForHacknetNode:1e3,BaseCostForHacknetNodeCore:5e5,HacknetNodeMoneyGainPerLevel:1.6,HacknetNodePurchaseNextMult:1.85,HacknetNodeUpgradeLevelMult:1.04,HacknetNodeUpgradeRamMult:1.28,HacknetNodeUpgradeCoreMult:1.48,HacknetNodeMaxLevel:200,HacknetNodeMaxRam:64,HacknetNodeMaxCores:16,BaseFavorToDonate:150,DonateMoneyToRepDivisor:1e6,FactionReputationToFavorBase:500,FactionReputationToFavorMult:1.02,CompanyReputationToFavorBase:500,CompanyReputationToFavorMult:1.02,NeuroFluxGovernorLevelMult:1.14,ScriptBaseRamCost:1.6,ScriptDomRamCost:25,ScriptWhileRamCost:0,ScriptForRamCost:0,ScriptIfRamCost:0,ScriptHackRamCost:.1,ScriptHackAnalyzeRamCost:1,ScriptGrowRamCost:.15,ScriptGrowthAnalyzeRamCost:1,ScriptWeakenRamCost:.15,ScriptScanRamCost:.2,ScriptPortProgramRamCost:.05,ScriptRunRamCost:1,ScriptExecRamCost:1.3,ScriptSpawnRamCost:2,ScriptScpRamCost:.6,ScriptKillRamCost:.5,ScriptHasRootAccessRamCost:.05,ScriptGetHostnameRamCost:.05,ScriptGetHackingLevelRamCost:.05,ScriptGetMultipliersRamCost:4,ScriptGetServerRamCost:.1,ScriptFileExistsRamCost:.1,ScriptIsRunningRamCost:.1,ScriptHacknetNodesRamCost:4,ScriptHNUpgLevelRamCost:.4,ScriptHNUpgRamRamCost:.6,ScriptHNUpgCoreRamCost:.8,ScriptGetStockRamCost:2,ScriptBuySellStockRamCost:2.5,ScriptGetPurchaseServerRamCost:.25,ScriptPurchaseServerRamCost:2.25,ScriptGetPurchasedServerLimit:.05,ScriptGetPurchasedServerMaxRam:.05,ScriptRoundRamCost:.05,ScriptReadWriteRamCost:1,ScriptArbScriptRamCost:1,ScriptGetScriptRamCost:.1,ScriptGetHackTimeRamCost:.05,ScriptGetFavorToDonate:.1,ScriptCodingContractBaseRamCost:10,ScriptSingularityFn1RamCost:1,ScriptSingularityFn2RamCost:2,ScriptSingularityFn3RamCost:3,ScriptSingularityFnRamMult:2,ScriptGangApiBaseRamCost:4,ScriptBladeburnerApiBaseRamCost:4,NumNetscriptPorts:20,HomeComputerMaxRam:1073741824,ServerBaseGrowthRate:1.03,ServerMaxGrowthRate:1.0035,ServerFortifyAmount:.002,ServerWeakenAmount:.05,PurchasedServerLimit:25,PurchasedServerMaxRam:1048576,AugmentationCostMultiplier:5,AugmentationRepMultiplier:2.5,MultipleAugMultiplier:1.9,TorRouterCost:2e5,InfiltrationBribeBaseAmount:1e5,InfiltrationMoneyValue:5e3,InfiltrationRepValue:1.4,WSEAccountCost:2e8,TIXAPICost:5e9,MarketData4SCost:1e9,MarketDataTixApi4SCost:25e9,StockMarketCommission:1e5,HospitalCostPerHp:1e5,IntelligenceCrimeWeight:.05,IntelligenceInfiltrationWeight:.1,IntelligenceCrimeBaseExpGain:.001,IntelligenceProgramBaseExpGain:500,IntelligenceTerminalHackBaseExpGain:200,IntelligenceSingFnBaseExpGain:.002,IntelligenceClassBaseExpGain:1e-6,IntelligenceHackingMissionBaseExpGain:.03,HackingMissionRepToDiffConversion:1e4,HackingMissionRepToRewardConversion:7,HackingMissionSpamTimeIncrease:25e3,HackingMissionTransferAttackIncrease:1.05,HackingMissionMiscDefenseIncrease:1.05,HackingMissionDifficultyToHacking:135,HackingMissionHowToPlay:"Hacking missions are a minigame that, if won, will reward you with faction reputation.

In this game you control a set of Nodes and use them to try and defeat an enemy. Your Nodes are colored blue, while the enemy's are red. There are also other nodes on the map colored gray that initially belong to neither you nor the enemy. The goal of the game is to capture all of the enemy's Database nodes within the time limit. If you fail to do this, you will lose.

Each Node has three stats: Attack, Defense, and HP. There are five different actions that a Node can take:

Attack - Targets an enemy Node and lowers its HP. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's defense.

Scan - Targets an enemy Node and lowers its Defense. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's defense.

Weaken - Targets an enemy Node and lowers its Attack. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's defense.

Fortify - Raises the Node's Defense. The effectiveness is determined by your hacking level.

Overflow - Raises the Node's Attack but lowers its Defense. The effectiveness is determined by your hacking level.

Note that when determining the effectiveness of the above actions, the TOTAL Attack or Defense of the team is used, not just the Attack/Defense of the individual Node that is performing the action.

To capture a Node, you must lower its HP down to 0.

There are six different types of Nodes:

CPU Core - These are your main Nodes that are used to perform actions. Capable of performing every action

Firewall - Nodes with high defense. These Nodes can 'Fortify'

Database - A special type of Node. The player's objective is to conquer all of the enemy's Database Nodes within the time limit. These Nodes cannot perform any actions

Spam - Conquering one of these Nodes will slow the enemy's trace, giving the player additional time to complete the mission. These Nodes cannot perform any actions

Transfer - Conquering one of these nodes will increase the Attack of all of your CPU Cores by a small fixed percentage. These Nodes are capable of performing every action except the 'Attack' action

Shield - Nodes with high defense. These Nodes can 'Fortify'

To assign an action to a Node, you must first select one of your Nodes. This can be done by simply clicking on it. Double-clicking a node will select all of your Nodes of the same type (e.g. select all CPU Core Nodes or all Transfer Nodes). Note that only Nodes that can perform actions (CPU Core, Transfer, Shield, Firewall) can be selected. Selected Nodes will be denoted with a white highlight. After selecting a Node or multiple Nodes, select its action using the Action Buttons near the top of the screen. Every action also has a corresponding keyboard shortcut.

For certain actions such as attacking, scanning, and weakening, the Node performing the action must have a target. To target another node, simply click-and-drag from the 'source' Node to a target. A Node can only have one target, and you can target any Node that is adjacent to one of your Nodes (immediately above, below, or to the side. NOT diagonal). Furthermore, only CPU Cores and Transfer Nodes can target, since they are the only ones that can perform the related actions. To remove a target, you can simply click on the line that represents the connection between one of your Nodes and its target. Alternatively, you can select the 'source' Node and click the 'Drop Connection' button, or press 'd'.

Other Notes:

-Whenever a miscellenaous Node (not owned by the player or enemy) is conquered, the defense of all remaining miscellaneous Nodes that are not actively being targeted will increase by a fixed percentage.

-Whenever a Node is conquered, its stats are significantly reduced

-Miscellaneous Nodes slowly raise their defense over time

-Nodes slowly regenerate health over time.",MillisecondsPer20Hours:72e6,GameCyclesPer20Hours:36e4,MillisecondsPer10Hours:36e6,GameCyclesPer10Hours:18e4,MillisecondsPer8Hours:288e5,GameCyclesPer8Hours:144e3,MillisecondsPer4Hours:144e5,GameCyclesPer4Hours:72e3,MillisecondsPer2Hours:72e5,GameCyclesPer2Hours:36e3,MillisecondsPerHour:36e5,GameCyclesPerHour:18e3,MillisecondsPerHalfHour:18e5,GameCyclesPerHalfHour:9e3,MillisecondsPerQuarterHour:9e5,GameCyclesPerQuarterHour:4500,MillisecondsPerFiveMinutes:3e5,GameCyclesPerFiveMinutes:1500,FactionWorkHacking:"Faction Hacking Work",FactionWorkField:"Faction Field Work",FactionWorkSecurity:"Faction Security Work",WorkTypeCompany:"Working for Company",WorkTypeCompanyPartTime:"Working for Company part-time",WorkTypeFaction:"Working for Faction",WorkTypeCreateProgram:"Working on Create a Program",WorkTypeStudyClass:"Studying or Taking a class at university",WorkTypeCrime:"Committing a crime",ClassStudyComputerScience:"studying Computer Science",ClassDataStructures:"taking a Data Structures course",ClassNetworks:"taking a Networks course",ClassAlgorithms:"taking an Algorithms course",ClassManagement:"taking a Management course",ClassLeadership:"taking a Leadership course",ClassGymStrength:"training your strength at a gym",ClassGymDefense:"training your defense at a gym",ClassGymDexterity:"training your dexterity at a gym",ClassGymAgility:"training your agility at a gym",ClassDataStructuresBaseCost:40,ClassNetworksBaseCost:80,ClassAlgorithmsBaseCost:320,ClassManagementBaseCost:160,ClassLeadershipBaseCost:320,ClassGymBaseCost:120,CrimeShoplift:"shoplift",CrimeRobStore:"rob a store",CrimeMug:"mug someone",CrimeLarceny:"commit larceny",CrimeDrugs:"deal drugs",CrimeBondForgery:"forge corporate bonds",CrimeTraffickArms:"traffick illegal arms",CrimeHomicide:"commit homicide",CrimeGrandTheftAuto:"commit grand theft auto",CrimeKidnap:"kidnap someone for ransom",CrimeAssassination:"assassinate a high-profile target",CrimeHeist:"pull off the ultimate heist",CodingContractBaseFactionRepGain:2500,CodingContractBaseCompanyRepGain:4e3,CodingContractBaseMoneyGain:75e6,TotalNumBitNodes:24,LatestUpdate:"\n v0.44.1\n * Duplicate Sleeve changes:\n ** You can now purchase Augmentations for your Duplicate Sleeves\n ** Sleeves are now assigned to Shock Recovery task by default\n ** Shock Recovery and Synchronize tasks are now twice as effective\n\n * Changed documentation so that Netscript functions are own their own pages. Sorry if this is annoying, it was necessary for properly cross-referencing\n * Officially deprecated the Wiki (the fandom site). Use the 'readthedocs' Documentation instead\n * Bug Fix: 'rm' Terminal and Netscript commands now work on non-program files that have '.exe' in the name (by Github user MasonD)\n * Bug Fix: The 'Find All Valid Math Expressions' Coding Contract should now properly ignore whitespace in answers\n * Bug Fix: The 'Merge Overlapping Intervals' Coding Contract should now properly accept 2D arrays when being attempted through Netscript\n "}},function(e,t,n){"use strict";function i(e,t={}){const n=document.createElement(e);return void 0!==t.id&&(n.id=t.id),void 0!==t.class&&(n.className=t.class),void 0!==t.innerHTML&&(n.innerHTML=t.innerHTML),void 0!==t.innerText&&(n.innerText=t.innerText),void 0!==t.tabIndex&&(n.tabIndex=t.tabIndex),function(e,t){void 0!==t.text&&(e.text=t.text),void 0!==t.href&&(e.href=t.href),void 0!==t.target&&(e.target=t.target)}(n,t),function(e,t){void 0!==t.name&&(e.name=t.name),void 0!==t.value&&(e.value=t.value),void 0!==t.type&&(e.type=t.type),void 0!==t.checked&&(e.checked=t.checked),void 0!==t.pattern&&(e.pattern=t.pattern),void 0!==t.maxLength&&(e.maxLength=t.maxLength),void 0!==t.placeholder&&(e.placeholder=t.placeholder),void 0!==t.max&&(e.max=t.max),void 0!==t.min&&(e.min=t.min),void 0!==t.step&&(e.step=t.step)}(n,t),function(e,t){void 0!==t.for&&(e.htmlFor=t.for)}(n,t),function(e,t){void 0!==t.clickListener&&e.addEventListener("click",t.clickListener),void 0!==t.inputListener&&e.addEventListener("input",t.inputListener),void 0!==t.changeListener&&e.addEventListener("change",t.changeListener),void 0!==t.onkeyup&&e.addEventListener("keyup",t.onkeyup),void 0!==t.onkeydown&&e.addEventListener("keydown",t.onkeydown),void 0!==t.onfocus&&e.addEventListener("focus",t.onfocus)}(n,t),function(e,t){void 0!==t.display&&(e.style.display=t.display),void 0!==t.visibility&&(e.style.visibility=t.visibility),void 0!==t.margin&&(e.style.margin=t.margin),void 0!==t.marginLeft&&(e.style.marginLeft=t.marginLeft),void 0!==t.marginTop&&(e.style.marginTop=t.marginTop),void 0!==t.padding&&(e.style.padding=t.padding),void 0!==t.color&&(e.style.color=t.color),void 0!==t.border&&(e.style.border=t.border),void 0!==t.float&&(e.style.cssFloat=t.float),void 0!==t.fontSize&&(e.style.fontSize=t.fontSize),void 0!==t.whiteSpace&&(e.style.whiteSpace=t.whiteSpace),void 0!==t.width&&(e.style.width=t.width),void 0!==t.backgroundColor&&(e.style.backgroundColor=t.backgroundColor),void 0!==t.position&&(e.style.position=t.position),void 0!==t.overflow&&(e.style.overflow=t.overflow)}(n,t),function(e,t){void 0!==t.tooltip&&""!==t.tooltip?(e.className+=" tooltip",e.appendChild(i("span",{class:"tooltiptext",innerHTML:t.tooltip}))):void 0!==t.tooltipleft?(e.className+=" tooltip",e.appendChild(i("span",{class:"tooltiptextleft",innerHTML:t.tooltipleft}))):void 0!==t.tooltipsmall?(e.className+=" tooltip",e.appendChild(i("span",{class:"tooltiptext smallfont",innerHTML:t.tooltipsmall}))):void 0!==t.tooltiplow&&(e.className+="tooltip",e.appendChild(i("span",{class:"tooltiptextlow",innerHTML:t.tooltiplow})))}(n,t),n}Object.defineProperty(t,"__esModule",{value:!0}),t.createElement=i},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(97);n(414),n(413),n(412),n(411),n(410),n(409),n(408),n(407),n(406),n(405),n(404),n(403),n(402),n(401);t.numeralWrapper=new class{constructor(){this.defaultLocale="en",this.defaultLocale="en"}updateLocale(e){return null!=i.locale(e)||(console.warn(`Invalid locale for numeral: ${e}`),i.locale(this.defaultLocale),!1)}format(e,t){return Math.abs(e)<1e-6&&(e=0),i(e).format(t)}formatBigNumber(e){return this.format(e,"0.000a")}formatMoney(e){return this.format(e,"$0.000a")}formatPercentage(e,t=2){const n="0."+"0".repeat(t)+"%";return this.format(e,n)}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(48);function a(e){return e.every(i.isString)}t.replaceAt=function(e,t,n){return e.substr(0,t)+n+e.substr(t+n.length)},t.convertTimeMsToTimeElapsedString=function(e){const t=Math.floor(e/1e3),n=Math.floor(t/86400),i=t%86400,a=Math.floor(i/3600),r=i%3600,o=Math.floor(r/60);let s="";return n>0&&(s+=`${n} days `),a>0&&(s+=`${a} hours `),o>0&&(s+=`${o} minutes `),s+=`${r%60} seconds`},t.longestCommonStart=function(e){if(!a(e))return"";if(0===e.length)return"";const t=e.concat().sort(),n=t[0],i=t[t.length-1],r=n.length;let o=0;const s=(e,t)=>e.toUpperCase()===t.toUpperCase();for(;o=0;e--)if(1===n[e].nodeType)return!0;return!1},t.generateRandomString=function(e){let t="";const n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";for(let i=0;i{t.delay=null,n()},e),t.delayResolve=n})}function h(e,t,n=null){var i="";null!=n&&(i=" (Line "+function(e,t){var n=t.scriptRef.codeCode();try{return((n=n.substring(0,e.start)).match(/\n/g)||[]).length+1}catch(e){return-1}}(n,e)+")");return"|"+e.serverIp+"|"+e.name+"|"+t+i}function g(e,t,n,a,o=1){if(null!=(p=Object(r.c)(t,n,e)))return a.scriptRef.log(t+" is already running on "+e.hostname),Promise.resolve(!1);for(var s=0;sm?(a.scriptRef.log("Cannot run script "+t+"(t="+o+") on "+e.hostname+" because there is not enough available RAM!"),Promise.resolve(!1)):(null==a.disableLogs.ALL&&null==a.disableLogs.exec&&null==a.disableLogs.run&&null==a.disableLogs.spawn&&a.scriptRef.log("Running script: "+t+" on "+e.hostname+" with "+o+" threads and args: "+Object(l.arrayToString)(n)+". May take a few seconds to start up..."),(p=new r.a(c,n)).threads=o,e.runningScripts.push(p),Object(i.c)(p,e),Promise.resolve(!0))}return a.scriptRef.log("Could not find script "+t+" on "+e.hostname),Promise.resolve(!1)}function _(e){if(!Object(u.isString)(e))return!1;let t=e.split("|");if(4!=t.length)return!1;var n=t[1];return!!Object(c.isValidIPAddress)(n)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(62);function a(e,t={}){const n=`color: ${null!=t.color?t.color:"var(--my-font-color)"}; background-color:var(--my-background-color);${void 0===t.id?" white-space:pre-wrap;":""}`,a=`${e}`;i.getElementById("terminal-input").insertAdjacentHTML("beforebegin",a),function(){const e=i.getElementById("terminal-container");e.scrollTop=e.scrollHeight}()}t.post=function(e){a(e)},t.postError=function(e){a(`ERROR: ${e}`,{color:"#ff2929"})},t.hackProgressBarPost=function(e){a(e,{id:"hack-progress-bar"})},t.hackProgressPost=function(e){a(e,{id:"hack-progress"})}},function(e,t,n){"use strict";n.r(t),function(e){n.d(t,"dialogBoxCreate",function(){return r}),n.d(t,"dialogBoxOpened",function(){return a});let i=[];e(document).click(function(t){a&&i.length>=1&&(e(t.target).closest(i[0]).length||(i[0].remove(),i.splice(0,1),0==i.length?a=!1:i[0].style.visibility="visible"))}),e(document).on("click",".dialog-box-close-button",function(e){a&&i.length>=1&&(i[0].remove(),i.splice(0,1),0==i.length?a=!1:i[0].style.visibility="visible")});var a=!1;function r(e,t=!1){var n=document.createElement("div");n.setAttribute("class","dialog-box-container");var r=document.createElement("div");r.setAttribute("class","dialog-box-content");var o,s=document.createElement("span");s.setAttribute("class","dialog-box-close-button"),s.innerHTML="×",t?(o=document.createElement("pre")).innerHTML=e:(o=document.createElement("p")).innerHTML=e.replace(/(?:\r\n|\r|\n)/g,"
"),r.appendChild(s),r.appendChild(o),n.appendChild(r),document.body.appendChild(n),i.length>=1&&(n.style.visibility="hidden"),i.push(n),setTimeout(function(){a=!0},400)}}.call(this,n(73))},function(e,t,n){"use strict";n.r(t),function(e){n.d(t,"Engine",function(){return oe});var i=n(4),a=n(94),r=n(79),o=n(13),s=n(66),l=n(6),c=n(70),u=n(34),p=n(159),m=n(119),d=n(107),h=(n(33),n(19)),g=n(69),_=n(1),y=n(142),f=n(15),b=n(51),E=n(52),v=n(110),k=n(5),C=n(78),O=n(38),T=n(100),S=n(44),M=n(65),P=n(47),A=n(35),w=n(0),x=n(104),R=(n(28),n(114)),N=n(68),I=n(84),L=n(32),D=n(11),B=n(18),j=n(86),W=n(85),F=n(36),U=n(24),H=n(59),G=n(98),K=n(121),q=n(141),$=n(87),Y=n(158),z=n(12),V=n(3),J=n(157),X=n(156),Q=n(26),Z=n(9),ee=n(101),te=(n(16),n(27)),ne=(n(21),n(2)),ie=n(40),ae=n(140),re=n(29);n(272),n(230),n(228),n(226),n(224),n(222),n(220),n(218),n(216),n(214),n(212),n(210),n(208),n(206),n(204),n(202),n(200),n(198),n(196),n(194),n(192),n(190),n(188);e(document).keydown(function(e){if(!0!==B.Settings.DisableHotkeys){try{if(Object(L.d)().isFocused())return}catch(e){}if(!(w.a.isWorking||N.b||M.c||m.a))if(84==e.keyCode&&e.altKey)e.preventDefault(),oe.loadTerminalContent();else if(e.keyCode===re.KEY.C&&e.altKey)e.preventDefault(),oe.loadCharacterContent();else if(e.keyCode===re.KEY.E&&e.altKey)e.preventDefault(),oe.loadScriptEditorContent();else if(e.keyCode===re.KEY.S&&e.altKey)e.preventDefault(),oe.loadActiveScriptsContent();else if(e.keyCode===re.KEY.H&&e.altKey)e.preventDefault(),oe.loadHacknetNodesContent();else if(e.keyCode===re.KEY.W&&e.altKey)e.preventDefault(),oe.loadWorldContent();else if(e.keyCode===re.KEY.J&&e.altKey)e.preventDefault(),oe.loadJobContent();else if(e.keyCode===re.KEY.R&&e.altKey)e.preventDefault(),oe.loadTravelContent();else if(e.keyCode===re.KEY.P&&e.altKey)e.preventDefault(),oe.loadCreateProgramContent();else if(e.keyCode===re.KEY.F&&e.altKey){if(z.routing.isOn(z.Page.Terminal)&&E.a.ENABLE_BASH_HOTKEYS)return;e.preventDefault(),oe.loadFactionsContent()}else e.keyCode===re.KEY.A&&e.altKey?(e.preventDefault(),oe.loadAugmentationsContent()):e.keyCode===re.KEY.U&&e.altKey&&(e.preventDefault(),oe.loadTutorialContent());e.keyCode===re.KEY.O&&e.altKey&&(e.preventDefault(),Object(ee.b)())}});const oe={version:"",Debug:!0,overview:new p.a,Clickables:{saveMainMenuButton:null,deleteMainMenuButton:null},Display:{progress:null,statusText:null,hacking_skill:null,terminalContent:null,characterContent:null,scriptEditorContent:null,activeScriptsContent:null,hacknetNodesContent:null,worldContent:null,createProgramContent:null,factionsContent:null,factionContent:null,factionAugmentationsContent:null,augmentationsContent:null,tutorialContent:null,infiltrationContent:null,stockMarketContent:null,locationContent:null,workInProgressContent:null,redPillContent:null,cinematicTextContent:null,missionContent:null,characterInfo:null},_lastUpdate:(new Date).getTime(),_idleSpeed:200,loadTerminalContent:function(){oe.hideAllContent(),oe.Display.terminalContent.style.display="block",z.routing.navigateTo(z.Page.Terminal),Q.MainMenuLinks.Terminal.classList.add("active")},loadCharacterContent:function(){oe.hideAllContent(),oe.Display.characterContent.style.display="block",oe.updateCharacterInfo(),z.routing.navigateTo(z.Page.CharacterInfo),Q.MainMenuLinks.Stats.classList.add("active")},loadScriptEditorContent:function(e="",t=""){oe.hideAllContent(),oe.Display.scriptEditorContent.style.display="block";try{Object(L.d)().openScript(e,t)}catch(e){Object(ie.exceptionAlert)(e)}Object(L.h)(),z.routing.navigateTo(z.Page.ScriptEditor),Q.MainMenuLinks.ScriptEditor.classList.add("active")},loadActiveScriptsContent:function(){oe.hideAllContent(),oe.Display.activeScriptsContent.style.display="block",Object(r.c)(),z.routing.navigateTo(z.Page.ActiveScripts),Q.MainMenuLinks.ActiveScripts.classList.add("active")},loadHacknetNodesContent:function(){oe.hideAllContent(),oe.Display.hacknetNodesContent.style.display="block",Object(C.b)(),z.routing.navigateTo(z.Page.HacknetNodes),Q.MainMenuLinks.HacknetNodes.classList.add("active")},loadWorldContent:function(){oe.hideAllContent(),oe.Display.worldContent.style.display="block",oe.displayWorldInfo(),z.routing.navigateTo(z.Page.World),Q.MainMenuLinks.City.classList.add("active")},loadCreateProgramContent:function(){oe.hideAllContent(),oe.Display.createProgramContent.style.display="block",Object(R.a)(),z.routing.navigateTo(z.Page.CreateProgram),Q.MainMenuLinks.CreateProgram.classList.add("active")},loadFactionsContent:function(){oe.hideAllContent(),oe.Display.factionsContent.style.display="block",oe.displayFactionsInfo(),z.routing.navigateTo(z.Page.Factions),Q.MainMenuLinks.Factions.classList.add("active")},loadFactionContent:function(){oe.hideAllContent(),oe.Display.factionContent.style.display="block",z.routing.navigateTo(z.Page.Faction)},loadAugmentationsContent:function(){oe.hideAllContent(),oe.Display.augmentationsContent.style.display="block",Object(s.c)(oe.Display.augmentationsContent),z.routing.navigateTo(z.Page.Augmentations),Q.MainMenuLinks.Augmentations.classList.add("active")},loadTutorialContent:function(){oe.hideAllContent(),oe.Display.tutorialContent.style.display="block",z.routing.navigateTo(z.Page.Tutorial),Q.MainMenuLinks.Tutorial.classList.add("active")},loadDevMenuContent:function(){oe.hideAllContent(),Object(y.b)(),z.routing.navigateTo(z.Page.DevMenu),Q.MainMenuLinks.DevMenu.classList.add("active")},loadLocationContent:function(){oe.hideAllContent(),oe.Display.locationContent.style.display="block";try{Object(v.a)()}catch(e){Object(ie.exceptionAlert)(e),console.error(e)}z.routing.navigateTo(z.Page.Location)},loadTravelContent:function(){switch(w.a.city){case k.Locations.Aevum:w.a.location=k.Locations.AevumTravelAgency;break;case k.Locations.Chongqing:w.a.location=k.Locations.ChongqingTravelAgency;break;case k.Locations.Sector12:w.a.location=k.Locations.Sector12TravelAgency;break;case k.Locations.NewTokyo:w.a.location=k.Locations.NewTokyoTravelAgency;break;case k.Locations.Ishima:w.a.location=k.Locations.IshimaTravelAgency;break;case k.Locations.Volhaven:w.a.location=k.Locations.VolhavenTravelAgency;break;default:Object(Z.dialogBoxCreate)("ERROR: Invalid city. This is a bug please contact game dev")}oe.loadLocationContent()},loadJobContent:function(){""!=w.a.companyName?(w.a.location=w.a.companyName,oe.loadLocationContent()):Object(Z.dialogBoxCreate)("You do not currently have a job! You can visit various companies in the city and try to find a job.")},loadWorkInProgressContent:function(){oe.hideAllContent(),document.getElementById("mainmenu-container").style.visibility="hidden",oe.Display.workInProgressContent.style.display="block",z.routing.navigateTo(z.Page.WorkInProgress)},loadRedPillContent:function(){oe.hideAllContent(),document.getElementById("mainmenu-container").style.visibility="hidden",oe.Display.redPillContent.style.display="block",z.routing.navigateTo(z.Page.RedPill)},loadCinematicTextContent:function(){oe.hideAllContent(),document.getElementById("mainmenu-container").style.visibility="hidden",oe.Display.cinematicTextContent.style.display="block",z.routing.navigateTo(z.Page.CinematicText)},loadInfiltrationContent:function(){oe.hideAllContent(),oe.Display.infiltrationContent.style.display="block",z.routing.navigateTo(z.Page.Infiltration)},loadStockMarketContent:function(){oe.hideAllContent(),oe.Display.stockMarketContent.style.display="block",z.routing.navigateTo(z.Page.StockMarket),Object(U.g)()},loadGangContent:function(){oe.hideAllContent(),document.getElementById("gang-container")||w.a.inGang()?(w.a.gang.displayGangContent(w.a),z.routing.navigateTo(z.Page.Gang)):(oe.loadTerminalContent(),z.routing.navigateTo(z.Page.Terminal))},loadMissionContent:function(){oe.hideAllContent(),document.getElementById("mainmenu-container").style.visibility="hidden",document.getElementById("character-overview-wrapper").style.visibility="hidden",oe.Display.missionContent.style.display="block",z.routing.navigateTo(z.Page.Mission)},loadCorporationContent:function(){w.a.corporation instanceof g.a&&(oe.hideAllContent(),document.getElementById("character-overview-wrapper").style.visibility="hidden",w.a.corporation.createUI(),z.routing.navigateTo(z.Page.Corporation))},loadBladeburnerContent:function(){if(w.a.bladeburner instanceof u.a)try{oe.hideAllContent(),z.routing.navigateTo(z.Page.Bladeburner),w.a.bladeburner.createContent()}catch(e){Object(ie.exceptionAlert)(e)}},loadSleevesContent:function(){try{oe.hideAllContent(),z.routing.navigateTo(z.Page.Sleeves),Object(K.createSleevesPage)(w.a)}catch(e){Object(ie.exceptionAlert)(e)}},loadResleevingContent:function(){try{oe.hideAllContent(),z.routing.navigateTo(z.Page.Resleeves),Object(q.createResleevesPage)(w.a)}catch(e){Object(ie.exceptionAlert)(e)}},hideAllContent:function(){oe.Display.terminalContent.style.display="none",oe.Display.characterContent.style.display="none",oe.Display.scriptEditorContent.style.display="none",oe.Display.activeScriptsContent.style.display="none",oe.Display.hacknetNodesContent.style.display="none",oe.Display.worldContent.style.display="none",oe.Display.createProgramContent.style.display="none",oe.Display.factionsContent.style.display="none",oe.Display.factionContent.style.display="none",oe.Display.factionAugmentationsContent.style.display="none",oe.Display.augmentationsContent.style.display="none",oe.Display.tutorialContent.style.display="none",oe.Display.locationContent.style.display="none",oe.Display.workInProgressContent.style.display="none",oe.Display.redPillContent.style.display="none",oe.Display.cinematicTextContent.style.display="none",oe.Display.infiltrationContent.style.display="none",oe.Display.stockMarketContent.style.display="none",oe.Display.missionContent.style.display="none",document.getElementById("gang-container")&&(document.getElementById("gang-container").style.display="none"),w.a.inGang()&&w.a.gang.clearUI(),w.a.corporation instanceof g.a&&w.a.corporation.clearUI(),w.a.bladeburner instanceof u.a&&w.a.bladeburner.clearContent(),Object(q.clearResleevesPage)(),Object(K.clearSleevesPage)(),oe.aevumLocationsList.style.display="none",oe.chongqingLocationsList.style.display="none",oe.sector12LocationsList.style.display="none",oe.newTokyoLocationsList.style.display="none",oe.ishimaLocationsList.style.display="none",oe.volhavenLocationsList.style.display="none",Q.MainMenuLinks.Terminal.classList.remove("active"),Q.MainMenuLinks.ScriptEditor.classList.remove("active"),Q.MainMenuLinks.ActiveScripts.classList.remove("active"),Q.MainMenuLinks.CreateProgram.classList.remove("active"),Q.MainMenuLinks.Stats.classList.remove("active"),Q.MainMenuLinks.Factions.classList.remove("active"),Q.MainMenuLinks.Augmentations.classList.remove("active"),Q.MainMenuLinks.HacknetNodes.classList.remove("active"),Q.MainMenuLinks.Sleeves.classList.remove("active"),Q.MainMenuLinks.City.classList.remove("active"),Q.MainMenuLinks.Travel.classList.remove("active"),Q.MainMenuLinks.Job.classList.remove("active"),Q.MainMenuLinks.StockMarket.classList.remove("active"),Q.MainMenuLinks.Bladeburner.classList.remove("active"),Q.MainMenuLinks.Corporation.classList.remove("active"),Q.MainMenuLinks.Gang.classList.remove("active"),Q.MainMenuLinks.Tutorial.classList.remove("active"),Q.MainMenuLinks.Options.classList.remove("active"),Q.MainMenuLinks.DevMenu.classList.remove("active"),Object(y.a)()},displayCharacterOverviewInfo:function(){oe.overview.update();const e=document.getElementById("character-overview-save-button");B.Settings.AutosaveInterval?e.classList.remove("flashing-button"):e.classList.add("flashing-button")},updateCharacterInfo:function(){Object(Y.displayCharacterInfo)(oe.Display.characterInfo,w.a)},aevumLocationsList:null,chongqingLocationsList:null,sector12LocationsList:null,newTokyoLocationsList:null,ishimaLocationsList:null,volhavenLocationsList:null,displayWorldInfo:function(){oe.aevumLocationsList.style.display="none",oe.chongqingLocationsList.style.display="none",oe.sector12LocationsList.style.display="none",oe.newTokyoLocationsList.style.display="none",oe.ishimaLocationsList.style.display="none",oe.volhavenLocationsList.style.display="none",document.getElementById("world-city-name").innerHTML=w.a.city;document.getElementById("world-city-desc");switch(w.a.city){case k.Locations.Aevum:oe.aevumLocationsList.style.display="inline";break;case k.Locations.Chongqing:oe.chongqingLocationsList.style.display="inline";break;case k.Locations.Sector12:oe.sector12LocationsList.style.display="inline",3!==w.a.bitNodeN&&!P.e||8===w.a.bitNodeN?document.getElementById("sector12-cityhall-li").style.display="none":document.getElementById("sector12-cityhall-li").style.display="block";break;case k.Locations.NewTokyo:oe.newTokyoLocationsList.style.display="inline";break;case k.Locations.Ishima:oe.ishimaLocationsList.style.display="inline";break;case k.Locations.Volhaven:oe.volhavenLocationsList.style.display="inline";break;default:console.log("Invalid city value in Player object!")}var e,t=document.getElementById("generic-locations-list");(t.style.display="inline",Object(te.removeChildrenFromElement)(t),(e=Object(ne.createElement)("li")).appendChild(Object(ne.createElement)("a",{innerText:"World Stock Exchange",class:"a-link-button",clickListener:()=>(w.a.location=k.Locations.WorldStockExchange,oe.loadStockMarketContent(),!1)})),t.appendChild(e),w.a.corporation instanceof g.a&&null==document.getElementById("location-corporation-button"))&&((e=Object(ne.createElement)("li")).appendChild(Object(ne.createElement)("a",{innerText:w.a.corporation.name,id:"location-corporation-button",class:"a-link-button",clickListener:()=>(oe.loadCorporationContent(),!1)})),t.appendChild(e));w.a.bladeburner instanceof u.a&&((e=Object(ne.createElement)("li")).appendChild(Object(ne.createElement)("a",{innerText:"Bladeburner Headquarters",class:"a-link-button",clickListener:()=>(oe.loadBladeburnerContent(),!1)})),t.appendChild(e))},displayFactionsInfo:function(){Object(te.removeChildrenFromElement)(oe.Display.factionsContent),oe.Display.factionsContent.appendChild(Object(ne.createElement)("h1",{innerText:"Factions"})),oe.Display.factionsContent.appendChild(Object(ne.createElement)("p",{innerText:"Lists all factions you have joined"}));var e=Object(ne.createElement)("ul");oe.Display.factionsContent.appendChild(Object(ne.createElement)("br"));for(var t=0;t(oe.loadFactionContent(),Object(b.a)(n),!1)})),e.appendChild(Object(ne.createElement)("br"))}();oe.Display.factionsContent.appendChild(e),oe.Display.factionsContent.appendChild(Object(ne.createElement)("br")),oe.Display.factionsContent.appendChild(Object(ne.createElement)("h1",{innerText:"Outstanding Faction Invitations"})),oe.Display.factionsContent.appendChild(Object(ne.createElement)("p",{width:"70%",innerText:"Lists factions you have been invited to, as well as factions you have previously rejected. You can accept these faction invitations at any time."}));var n=Object(ne.createElement)("ul");for(t=0;t{if(!t.isTrusted)return!1;Object(b.c)(f.Factions[e]);for(var n=0;n0&&(oe._lastUpdate=e-n,w.a.lastUpdate=e-n,oe.updateGame(t)),window.requestAnimationFrame(oe.idleTimer)},updateGame:function(e=1){var t=e*oe._idleSpeed;null==w.a.totalPlaytime&&(w.a.totalPlaytime=0),null==w.a.playtimeSinceLastAug&&(w.a.playtimeSinceLastAug=0),null==w.a.playtimeSinceLastBitnode&&(w.a.playtimeSinceLastBitnode=0),w.a.totalPlaytime+=t,w.a.playtimeSinceLastAug+=t,w.a.playtimeSinceLastBitnode+=t,!0===H.a.actionStarted&&(oe._totalActionTime=H.a.actionTime,oe._actionTimeLeft=H.a.actionTime,oe._actionInProgress=!0,oe._actionProgressBarCount=1,oe._actionProgressStr="[ ]",oe._actionTimeStr="Time left: ",H.a.actionStarted=!1),w.a.isWorking&&(w.a.workType==_.CONSTANTS.WorkTypeFaction?w.a.workForFaction(e):w.a.workType==_.CONSTANTS.WorkTypeCreateProgram?w.a.createProgramWork(e):w.a.workType==_.CONSTANTS.WorkTypeStudyClass?w.a.takeClass(e):w.a.workType==_.CONSTANTS.WorkTypeCrime?w.a.commitCrime(e):w.a.workType==_.CONSTANTS.WorkTypeCompanyPartTime?w.a.workPartTime(e):w.a.work(e)),w.a.hasWseAccount&&Object(U.m)(e),2==w.a.bitNodeN&&w.a.inGang()&&w.a.gang.process(e,w.a),M.c&&M.b&&M.b.process(e),w.a.corporation instanceof g.a&&w.a.corporation.storeCycles(e),w.a.bladeburner instanceof u.a&&w.a.bladeburner.storeCycles(e);for(let t=0;t0?(t.innerHTML=e,t.setAttribute("class","notification-on")):(t.innerHTML="",t.setAttribute("class","notification-off")),oe.Counters.createProgramNotifications=10}if(oe.Counters.checkFactionInvitations<=0){var n=w.a.checkForFactionInvitations();if(n.length>0){!1===w.a.firstFacInvRecvd&&(w.a.firstFacInvRecvd=!0,document.getElementById("factions-tab").style.display="list-item",document.getElementById("character-menu-header").click(),document.getElementById("character-menu-header").click());var i=n[Math.floor(Math.random()*n.length)];Object(b.b)(i)}oe.Counters.checkFactionInvitations=100}if(oe.Counters.passiveFactionGrowth<=0){var s=Math.floor(600-oe.Counters.passiveFactionGrowth);Object(b.d)(s),oe.Counters.passiveFactionGrowth=600}if(oe.Counters.messages<=0&&(Object(S.c)(),o.Augmentations[l.AugmentationNames.TheRedPill].owned?oe.Counters.messages=4500:oe.Counters.messages=150),oe.Counters.sCr<=0&&(w.a.hasWseAccount&&Object(U.q)(),oe.Counters.sCr=1500),oe.Counters.mechanicProcess<=0){if(w.a.corporation instanceof g.a&&w.a.corporation.process(),w.a.bladeburner instanceof u.a)try{w.a.bladeburner.process()}catch(e){Object(ie.exceptionAlert)("Exception caught in Bladeburner.process(): "+e)}oe.Counters.mechanicProcess=5}oe.Counters.contractGeneration<=0&&(Math.random()<=.25&&Object(d.b)(),oe.Counters.contractGeneration=3e3)},_totalActionTime:0,_actionTimeLeft:0,_actionTimeStr:"Time left: ",_actionProgressStr:"[ ]",_actionProgressBarCount:1,_actionInProgress:!1,updateHackProgress:function(e=1){var t=e*oe._idleSpeed;oe._actionTimeLeft-=t/1e3,oe._actionTimeLeft=Math.max(oe._actionTimeLeft,0);for(var n=Math.round(100*(1-oe._actionTimeLeft/oe._totalActionTime));2*oe._actionProgressBarCount<=n;)oe._actionProgressStr=Object(i.replaceAt)(oe._actionProgressStr,oe._actionProgressBarCount,"|"),oe._actionProgressBarCount+=1;oe._actionTimeStr="Time left: "+Math.max(0,Math.round(oe._actionTimeLeft)).toString()+"s",document.getElementById("hack-progress").innerHTML=oe._actionTimeStr,document.getElementById("hack-progress-bar").innerHTML=oe._actionProgressStr.replace(/ /g," "),n>=100&&(oe._actionInProgress=!1,H.a.finishAction())},closeMainMenuHeader:function(e){for(var t=0;t"+V.numeralWrapper.formatMoney(q)+" and your Hacknet Nodes generated "+V.numeralWrapper.formatMoney($)+"");var z=[t,n,a,o,m,d,x,R,N];w.a.firstFacInvRecvd?z.push(l):l.style.display="none",w.a.firstAugPurchased?z.push(p):p.style.display="none",""!==w.a.companyName?z.push(E):E.style.display="none",w.a.firstTimeTraveled?z.push(y):y.style.display="none",w.a.firstProgramAvailable?z.push(r):r.style.display="none",w.a.hasWseAccount?z.push(v):v.style.display="none",w.a.bladeburner instanceof u.a?z.push(k):k.style.display="none",w.a.corporation instanceof g.a?z.push(M):M.style.display="none",w.a.inGang()?z.push(A):A.style.display="none",oe.closeMainMenuHeader(z)}else{console.log("Initializing new game"),Object(c.initBitNodes)(),Object(c.initBitNodeMultipliers)(w.a),Object(j.c)(),Object(F.c)(),oe.setDisplayElements(),oe.start(),w.a.init(),Object(D.f)(),Object(h.initCompanies)(),Object(f.initFactions)(),Object(s.d)(),Object(S.d)(),Object(U.i)(),Object(T.a)(),Object(P.g)(),document.getElementById("hacking-menu-header").classList.toggle("opened"),document.getElementById("character-menu-header").classList.toggle("opened"),document.getElementById("world-menu-header").classList.toggle("opened"),document.getElementById("help-menu-header").classList.toggle("opened"),l.style.display="none",p.style.display="none",E.style.display="none",v.style.display="none",y.style.display="none",r.style.display="none",k.style.display="none",M.style.display="none",A.style.display="none",N.style.display="none",oe.openMainMenuHeader([t,n,a,o,m,d,x,R]),Object(O.c)(),Object(ae.removeLoadingScreen)()}Object(J.a)(),Object(L.g)(),H.a.resetTerminalInput()},setDisplayElements:function(){if(oe.Display.terminalContent=document.getElementById("terminal-container"),z.routing.navigateTo(z.Page.Terminal),oe.Display.characterContent=document.getElementById("character-container"),oe.Display.characterContent.style.display="none",oe.Display.scriptEditorContent=document.getElementById("script-editor-container"),oe.Display.scriptEditorContent.style.display="none",oe.Display.activeScriptsContent=document.getElementById("active-scripts-container"),oe.Display.activeScriptsContent.style.display="none",oe.Display.hacknetNodesContent=document.getElementById("hacknet-nodes-container"),oe.Display.hacknetNodesContent.style.display="none",oe.Display.worldContent=document.getElementById("world-container"),oe.Display.worldContent.style.display="none",oe.Display.createProgramContent=document.getElementById("create-program-container"),oe.Display.createProgramContent.style.display="none",oe.Display.factionsContent=document.getElementById("factions-container"),oe.Display.factionsContent.style.display="none",oe.Display.factionContent=document.getElementById("faction-container"),oe.Display.factionContent.style.display="none",oe.Display.factionAugmentationsContent=document.getElementById("faction-augmentations-container"),oe.Display.factionAugmentationsContent.style.display="none",oe.Display.augmentationsContent=document.getElementById("augmentations-container"),oe.Display.augmentationsContent.style.display="none",oe.Display.tutorialContent=document.getElementById("tutorial-container"),oe.Display.tutorialContent.style.display="none",oe.Display.infiltrationContent=document.getElementById("infiltration-container"),oe.Display.infiltrationContent.style.display="none",oe.Display.stockMarketContent=document.getElementById("stock-market-container"),oe.Display.stockMarketContent.style.display="none",oe.Display.missionContent=document.getElementById("mission-container"),oe.Display.missionContent.style.display="none",oe.Display.characterInfo=document.getElementById("character-content"),oe.aevumLocationsList=document.getElementById("aevum-locations-list"),oe.chongqingLocationsList=document.getElementById("chongqing-locations-list"),oe.sector12LocationsList=document.getElementById("sector12-locations-list"),oe.newTokyoLocationsList=document.getElementById("newtokyo-locations-list"),oe.ishimaLocationsList=document.getElementById("ishima-locations-list"),oe.volhavenLocationsList=document.getElementById("volhaven-locations-list"),oe.Display.locationContent=document.getElementById("location-container"),oe.Display.locationContent.style.display="none",oe.Display.workInProgressContent=document.getElementById("work-in-progress-container"),oe.Display.workInProgressContent.style.display="none",oe.Display.redPillContent=document.getElementById("red-pill-container"),oe.Display.redPillContent.style.display="none",oe.Display.cinematicTextContent=document.getElementById("cinematic-text-container"),oe.Display.cinematicTextContent.style.display="none",Object(v.b)(),!Object(Q.initializeMainMenuLinks)()){const e="Failed to initialize Main Menu Links. Please try refreshing the page. If that doesn't work, report the issue to the developer";return Object(ie.exceptionAlert)(new Error(e)),void console.error(e)}},init:function(){if(document.getElementById("import-game-link").onclick=function(){I.b.importGame()},!Object(X.initializeMainMenuHeaders)(w.a,!1)){const e="Failed to initialize Main Menu Headers. Please try refreshing the page. If that doesn't work, report the issue to the developer";return Object(ie.exceptionAlert)(new Error(e)),void console.error(e)}(Q.MainMenuLinks.Terminal.addEventListener("click",function(){return oe.loadTerminalContent(),!1}),Q.MainMenuLinks.ScriptEditor.addEventListener("click",function(){return oe.loadScriptEditorContent(),!1}),Q.MainMenuLinks.ActiveScripts.addEventListener("click",function(){return oe.loadActiveScriptsContent(),!1}),Q.MainMenuLinks.CreateProgram.addEventListener("click",function(){return oe.loadCreateProgramContent(),!1}),Q.MainMenuLinks.Stats.addEventListener("click",function(){return oe.loadCharacterContent(),!1}),Q.MainMenuLinks.Factions.addEventListener("click",function(){return oe.loadFactionsContent(),!1}),Q.MainMenuLinks.Augmentations.addEventListener("click",function(){return oe.loadAugmentationsContent(),!1}),Q.MainMenuLinks.HacknetNodes.addEventListener("click",function(){return oe.loadHacknetNodesContent(),!1}),Q.MainMenuLinks.Sleeves.addEventListener("click",function(){return oe.loadSleevesContent(),Q.MainMenuLinks.Sleeves.classList.add("active"),!1}),Q.MainMenuLinks.City.addEventListener("click",function(){return oe.loadWorldContent(),!1}),Q.MainMenuLinks.Travel.addEventListener("click",function(){return oe.loadTravelContent(),Q.MainMenuLinks.Travel.classList.add("active"),!1}),Q.MainMenuLinks.Job.addEventListener("click",function(){return oe.loadJobContent(),Q.MainMenuLinks.Job.classList.add("active"),!1}),Q.MainMenuLinks.StockMarket.addEventListener("click",function(){return oe.loadStockMarketContent(),Q.MainMenuLinks.StockMarket.classList.add("active"),!1}),Q.MainMenuLinks.Bladeburner.addEventListener("click",function(){return oe.loadBladeburnerContent(),!1}),Q.MainMenuLinks.Corporation.addEventListener("click",function(){return oe.loadCorporationContent(),Q.MainMenuLinks.Corporation.classList.add("active"),!1}),Q.MainMenuLinks.Gang.addEventListener("click",function(){return oe.loadGangContent(),!1}),Q.MainMenuLinks.Tutorial.addEventListener("click",function(){return oe.loadTutorialContent(),!1}),Q.MainMenuLinks.DevMenu.addEventListener("click",function(){return!1}),oe.ActiveScriptsList=document.getElementById("active-scripts-list"),oe.Clickables.saveMainMenuButton=document.getElementById("save-game-link"),oe.Clickables.saveMainMenuButton.addEventListener("click",function(){return I.b.saveGame(se),!1}),oe.Clickables.deleteMainMenuButton=document.getElementById("delete-game-link"),oe.Clickables.deleteMainMenuButton.addEventListener("click",function(){return I.b.deleteGame(se),!1}),document.getElementById("export-game-link").addEventListener("click",function(){return I.b.exportGame(),!1}),document.getElementById("character-overview-save-button").addEventListener("click",function(){return I.b.saveGame(se),!1}),document.getElementById("character-overview-options-button").addEventListener("click",function(){return Object(ee.b)(),!1}),Object(R.c)(),Object(H.b)(),w.a.isWorking)&&(document.getElementById("work-in-progress-cancel-button").addEventListener("click",function(){if(w.a.workType==_.CONSTANTS.WorkTypeFaction){f.Factions[w.a.currentWorkFactionName];w.a.finishFactionWork(!0)}else w.a.workType==_.CONSTANTS.WorkTypeCreateProgram?w.a.finishCreateProgramWork(!0):w.a.workType==_.CONSTANTS.WorkTypeStudyClass?w.a.finishClass():w.a.workType==_.CONSTANTS.WorkTypeCrime?w.a.finishCrime(!0):w.a.workType==_.CONSTANTS.WorkTypeCompanyPartTime?w.a.finishWorkPartTime():w.a.finishWork(!0)}),oe.loadWorkInProgressContent());document.getElementById("character-overview-container").style.display="block",document.getElementById("terminal-menu-link").removeAttribute("class"),document.getElementById("stats-menu-link").removeAttribute("class"),document.getElementById("create-script-menu-link").removeAttribute("class"),document.getElementById("active-scripts-menu-link").removeAttribute("class"),document.getElementById("hacknet-nodes-menu-link").removeAttribute("class"),document.getElementById("city-menu-link").removeAttribute("class"),document.getElementById("tutorial-menu-link").removeAttribute("class"),document.getElementById("copy-save-to-clipboard-link").addEventListener("click",function(){const e=I.b.getSaveString();if(navigator.clipboard)navigator.clipboard.writeText(e).then(function(){Object($.createStatusText)("Copied save to clipboard")},function(e){console.error("Unable to copy save data to clipboard using Async API"),Object($.createStatusText)("Failed to copy save")});else{const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.left="-9999px",document.body.appendChild(t),t.focus(),t.select();try{document.execCommand("copy")?Object($.createStatusText)("Copied save to clipboard"):Object($.createStatusText)("Failed to copy save")}catch(e){console.error("Unable to copy save data to clipboard using document.execCommand('copy')"),Object($.createStatusText)("Failed to copy save")}document.body.removeChild(t)}}),document.getElementById("debug-delete-scripts-link").addEventListener("click",function(){return console.log("Deleting running scripts on home computer"),w.a.getHomeComputer().runningScripts=[],Object(Z.dialogBoxCreate)("Forcefully deleted all running scripts on home computer. Please save and refresh page"),Object(ee.a)(),!1}),document.getElementById("debug-soft-reset").addEventListener("click",function(){return Object(Z.dialogBoxCreate)("Soft Reset!"),Object(x.a)(),Object(ee.a)(),!1})},start:function(){oe.idleTimer(),Object(A.f)()}};var se,le;window.onload=function(){if(!window.indexedDB)return oe.load(null);(le=window.indexedDB.open("bitburnerSave",1)).onerror=function(e){return console.log("Error opening indexedDB: "),console.log(e),oe.load(null)},le.onsuccess=function(e){console.log("Opening bitburnerSave database successful!");var t=(se=e.target.result).transaction(["savestring"]).objectStore("savestring").get("save");t.onerror=function(e){return console.log("Error in Database request to get savestring: "+e),oe.load(null)},t.onsuccess=function(e){oe.load(t.result)}},le.onupgradeneeded=function(e){e.target.result.createObjectStore("savestring")}}}.call(this,n(73))},function(e,t,n){"use strict";n.d(t,"f",function(){return y}),n.d(t,"h",function(){return f}),n.d(t,"k",function(){return b}),n.d(t,"j",function(){return E}),n.d(t,"i",function(){return k}),n.d(t,"g",function(){return C}),n.d(t,"a",function(){return O}),n.d(t,"c",function(){return T}),n.d(t,"e",function(){return S}),n.d(t,"d",function(){return _}),n.d(t,"b",function(){return v});var i=n(20),a=n(45),r=n(1),o=n(32),s=n(0),l=n(28),c=n(36),u=n(75),p=n(16),m=n(71),d=n(167),h=n(14),g=n(106);function _(e={ip:Object(m.a)(),hostname:""}){this.ip=e.ip?e.ip:Object(m.a)();for(var t=e.hostname,n=0,a="";null!=T(t+a);)a="-"+n,++n;this.hostname=t+a,this.organizationName=null!=e.organizationName?e.organizationName:"",this.isConnectedTo=null!=e.isConnectedTo&&e.isConnectedTo,this.hasAdminRights=null!=e.adminRights&&e.adminRights,this.purchasedByPlayer=null!=e.purchasedByPlayer&&e.purchasedByPlayer,this.manuallyHacked=!1,this.maxRam=null!=e.maxRam?e.maxRam:0,this.ramUsed=0,this.cpuCores=1,this.scripts=[],this.runningScripts=[],this.programs=[],this.messages=[],this.textFiles=[],this.contracts=[],this.dir=0,this.requiredHackingSkill=null!=e.requiredHackingSkill?e.requiredHackingSkill:1,this.moneyAvailable=null!=e.moneyAvailable?e.moneyAvailable*i.BitNodeMultipliers.ServerStartingMoney:0,this.moneyMax=25*this.moneyAvailable*i.BitNodeMultipliers.ServerMaxMoney,this.hackDifficulty=null!=e.hackDifficulty?e.hackDifficulty*i.BitNodeMultipliers.ServerStartingSecurity:1,this.baseDifficulty=this.hackDifficulty,this.minDifficulty=Math.max(1,Math.round(this.hackDifficulty/3)),this.serverGrowth=null!=e.serverGrowth?e.serverGrowth:1,this.serversOnNetwork=[],this.numOpenPortsRequired=null!=e.numOpenPortsRequired?e.numOpenPortsRequired:5,this.sshPortOpen=!1,this.ftpPortOpen=!1,this.smtpPortOpen=!1,this.httpPortOpen=!1,this.sqlPortOpen=!1,this.openPortCount=0}function y(){const e=[];for(let t=0;t<15;t++)e.push([]);const t=["hackDifficulty","moneyAvailable","requiredHackingSkill","serverGrowth"],n=e=>{switch(typeof e){case"number":return e;case"object":return Object(p.getRandomInt)(e.min,e.max);default:throw Error(`Do not know how to convert the type '${typeof e}' to a number`)}};for(const i of d.serverMetadata){const a={hostname:i.hostname,ip:Object(m.a)(),numOpenPortsRequired:i.numOpenPortsRequired,organizationName:i.organizationName};void 0!==i.maxRamExponent&&(a.maxRam=Math.pow(2,n(i.maxRamExponent)));for(const e of t)void 0!==i[e]&&(a[e]=n(i[e]));const r=new _(a);for(const e of i.literature||[])r.messages.push(e);void 0!==i.specialName&&c.a.addIp(i.specialName,r.ip),O(r),void 0!==i.networkLayer&&e[n(i.networkLayer)-1].push(r)}const i=e=>e[Math.floor(Math.random()*e.length)],a=(e,t)=>{for(const n of e)r=n,o=t(),r.serversOnNetwork.push(o.ip),o.serversOnNetwork.push(r.ip)};var r,o;a(e[0],()=>s.a.getHomeComputer());for(let t=1;ti(e[t-1]))}function f(e,t){let n=1+(r.CONSTANTS.ServerBaseGrowthRate-1)/e.hackDifficulty;n>r.CONSTANTS.ServerMaxGrowthRate&&(n=r.CONSTANTS.ServerMaxGrowthRate);const i=e.serverGrowth/100;return Math.log(t)/(Math.log(n)*s.a.hacking_grow_mult*i)}function b(e,t){const n=Math.max(Math.floor(t/450),0);var a=1+(r.CONSTANTS.ServerBaseGrowthRate-1)/e.hackDifficulty;a>r.CONSTANTS.ServerMaxGrowthRate&&(a=r.CONSTANTS.ServerMaxGrowthRate);const o=n*(e.serverGrowth/100)*i.BitNodeMultipliers.ServerGrowthRate;var l=Math.pow(a,o*s.a.hacking_grow_mult);l<1&&(console.log("WARN: serverGrowth calculated to be less than 1"),l=1);const c=e.moneyAvailable;if(e.moneyAvailable*=l,e.moneyMax&&isNaN(e.moneyAvailable)&&(e.moneyAvailable=e.moneyMax),e.moneyMax&&e.moneyAvailable>e.moneyMax&&(e.moneyAvailable=e.moneyMax),c!==e.moneyAvailable){let t=f(e,e.moneyAvailable/c);t=Math.max(0,t),e.fortify(2*r.CONSTANTS.ServerFortifyAmount*Math.ceil(t))}return e.moneyAvailable/c}function E(e){const t=e.programs.includes(l.Programs.BitFlume.name);e.programs.length=0,e.runningScripts=[],e.serversOnNetwork=[],e.isConnectedTo=!0,e.ramUsed=0,e.programs.push(l.Programs.NukeProgram.name),t&&e.programs.push(l.Programs.BitFlume.name),e.scripts.forEach(function(e){e.updateRamUsage()}),e.messages.length=0,e.messages.push("hackers-starting-handbook.lit")}_.prototype.setMaxRam=function(e){this.maxRam=e},_.prototype.getServerOnNetwork=function(e){if(!(e>this.serversOnNetwork.length))return v[this.serversOnNetwork[e]];console.log("Tried to get server on network that was out of range")},_.prototype.getScript=function(e){for(var t=0;t1e6&&(this.hackDifficulty=1e6)},_.prototype.fortify=function(e){this.hackDifficulty+=e,this.capDifficulty()},_.prototype.weaken=function(e){this.hackDifficulty-=e*i.BitNodeMultipliers.ServerWeakenRate,this.capDifficulty()},_.prototype.writeToScriptFile=function(e,t){var n={success:!1,overwritten:!1};if(!Object(o.e)(e))return n;for(let i=0;it.fn!==e.fn):this.contracts=this.contracts.filter(t=>t.fn!==e)},_.prototype.getContract=function(e){for(const t of this.contracts)if(t.fn===e)return t;return null},_.prototype.toJSON=function(){return Object(h.Generic_toJSON)("Server",this)},_.fromJSON=function(e){return Object(h.Generic_fromJSON)(_,e.data)},h.Reviver.constructors.Server=_;let v={};function k(){for(var e in v)delete v[e];v={}}function C(e){v=JSON.parse(e,h.Reviver)}function O(e){var t=e.ip;if(Object(m.b)(t))throw console.log("IP of server that's being added: "+t),console.log("Hostname of the server thats being added: "+e.hostname),console.log("The server that already has this IP is: "+v[t].hostname),new Error("Error: Trying to add a server with an existing IP");v[t]=e}function T(e){for(var t in v)if(v.hasOwnProperty(t)&&v[t].hostname==e)return v[t];return null}function S(e){return Object(g.isValidIPAddress)(e)?void 0!==v[e]?v[e]:null:T(e)}function M(e,t,n){this.s=e,this.p=t,this.c=[],this.n=n,this.d=t.d+1,this.scrs=[],this.pgms=[],this.msgs=[]}M.prototype.createSubdir=function(e){new M(this.s,this,e)},M.prototype.getPath=function(e){for(var t=[],n=this;null!==n;)t.unshift(n.n,"/"),n=n.parent;return t.unshift("/"),t.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e.Terminal="Terminal",e.CharacterInfo="CharacterInfo",e.ScriptEditor="ScriptEditor",e.ActiveScripts="ActiveScripts",e.HacknetNodes="HacknetNodes",e.World="World",e.CreateProgram="CreateProgram",e.Factions="Factions",e.Faction="Faction",e.Augmentations="Augmentations",e.Tutorial="Tutorial",e.DevMenu="Dev Menu",e.Location="Location",e.workInProgress="WorkInProgress",e.RedPill="RedPill",e.CinematicText="CinematicText",e.Infiltration="Infiltration",e.StockMarket="StockMarket",e.Gang="Gang",e.Mission="Mission",e.Corporation="Corporation",e.Bladeburner="Bladeburner",e.Sleeves="Sleeves",e.Resleeves="Re-sleeving"}(t.Page||(t.Page={}));t.routing=new class{constructor(){this.currentPage=null}isOn(e){return this.currentPage===e}navigateTo(e){this.currentPage=e}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Augmentations={}},function(e,t,n){"use strict";function i(e,t){var n;if(null==t)return console.log("Reviver WRONGLY called with key: "+e+", and value: "+t),0;if("object"==typeof t&&"string"==typeof t.ctor&&void 0!==t.data){if("AllServersMap"===t.ctor)return console.log("Converting AllServersMap for v0.43.1"),t.data;if("function"==typeof(n=i.constructors[t.ctor]||window[t.ctor])&&"function"==typeof n.fromJSON)return n.fromJSON(t)}return t}function a(e,t,n){var i,a;n||(n=Object.keys(t)),i={};for(let e=0;e{n=e,null!=t.Companies[n.name]&&console.warn(`Duplicate Company Position being defined: ${n.name}`),t.Companies[n.name]=new a.Company(n)});for(const n in t.Companies){const i=t.Companies[n];e[n]instanceof a.Company?(i.favor=e[n].favor,isNaN(i.favor)&&(i.favor=0)):i.favor=0}},t.loadCompanies=function(e){t.Companies=JSON.parse(e,r.Reviver)},t.companyExists=function(e){return t.Companies.hasOwnProperty(e)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BitNodeMultipliers={HackingLevelMultiplier:1,StrengthLevelMultiplier:1,DefenseLevelMultiplier:1,DexterityLevelMultiplier:1,AgilityLevelMultiplier:1,CharismaLevelMultiplier:1,ServerGrowthRate:1,ServerMaxMoney:1,ServerStartingMoney:1,ServerStartingSecurity:1,ServerWeakenRate:1,HomeComputerRamCost:1,PurchasedServerCost:1,PurchasedServerLimit:1,PurchasedServerMaxRam:1,CompanyWorkMoney:1,CrimeMoney:1,HacknetNodeMoney:1,ManualHackMoney:1,ScriptHackMoney:1,CodingContractMoney:1,ClassGymExpGain:1,CompanyWorkExpGain:1,CrimeExpGain:1,FactionWorkExpGain:1,HackExpGain:1,FactionPassiveRepGain:1,FactionWorkRepGain:1,RepToDonateToFaction:1,AugmentationMoneyCost:1,AugmentationRepCost:1,InfiltrationMoney:1,InfiltrationRep:1,FourSigmaMarketDataCost:1,FourSigmaMarketDataApiCost:1,CorporationValuation:1,BladeburnerRank:1,BladeburnerSkillCost:1,DaedalusAugsRequirement:1}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(62);t.clearEventListeners=function(e){try{let t;const n=(t="string"==typeof e?i.getElementById(e):e).cloneNode(!0);return null!==t.parentNode&&t.parentNode.replaceChild(n,t),n}catch(e){return console.error(e),null}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(21);t.yesNoBoxOpen=!1;const a=document.getElementById("yes-no-box-container"),r=document.getElementById("yes-no-box-text");function o(e){if(27===e.keyCode)s();else if(13===e.keyCode){const e=document.getElementById("yes-no-box-yes");e?e.click():console.error("Could not find YesNoBox Yes button DOM element")}}function s(){return a?a.style.display="none":console.error("Container not found for YesNoBox"),t.yesNoBoxOpen=!1,document.removeEventListener("keydown",o),!1}t.yesNoBoxHotkeyHandler=o,t.yesNoBoxClose=s,t.yesNoBoxGetYesButton=function(){return i.clearEventListeners("yes-no-box-yes")},t.yesNoBoxGetNoButton=function(){return i.clearEventListeners("yes-no-box-no")},t.yesNoBoxCreate=function(e){return!t.yesNoBoxOpen&&(t.yesNoBoxOpen=!0,r?r.innerHTML=e:console.error("Text element not found for YesNoBox"),a?a.style.display="flex":console.error("Container not found for YesNoBox"),document.addEventListener("keydown",o),!0)};const l=document.getElementById("yes-no-text-input-box-container"),c=document.getElementById("yes-no-text-input-box-input"),u=document.getElementById("yes-no-text-input-box-text");function p(e){if(27===e.keyCode)m();else if(13===e.keyCode){const e=document.getElementById("yes-no-text-input-box-yes");e?e.click():console.error("Could not find YesNoTxtInputBox Yes button DOM element")}}function m(){return null==l?(console.error("Container not found for YesNoTextInputBox"),!1):(l.style.display="none",t.yesNoBoxOpen=!1,c.value="",document.removeEventListener("keydown",p),!1)}t.yesNoTxtInpBoxHotkeyHandler=p,t.yesNoTxtInpBoxClose=m,t.yesNoTxtInpBoxGetYesButton=function(){return i.clearEventListeners("yes-no-text-input-box-yes")},t.yesNoTxtInpBoxGetNoButton=function(){return i.clearEventListeners("yes-no-text-input-box-no")},t.yesNoTxtInpBoxGetInput=function(){if(null==c)return console.error("Could not find YesNoTextInputBox input element"),"";let e=c.value;return e=e.replace(/\s+/g,"")},t.yesNoTxtInpBoxCreate=function(e){t.yesNoBoxOpen=!0,u&&(u.innerHTML=e),l?l.style.display="flex":console.error("Container not found for YesNoTextInputBox"),document.addEventListener("keydown",p),c.focus()}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(62),a=n(67);t.removeElementById=function(e){try{const t=i.getElementById(e);a.removeElement(t)}catch(e){}}},function(e,t,n){"use strict";n.d(t,"c",function(){return P}),n.d(t,"d",function(){return A}),n.d(t,"e",function(){return w}),n.d(t,"i",function(){return R}),n.d(t,"h",function(){return N}),n.d(t,"j",function(){return I}),n.d(t,"q",function(){return L}),n.d(t,"p",function(){return j}),n.d(t,"n",function(){return W}),n.d(t,"m",function(){return F}),n.d(t,"g",function(){return $}),n.d(t,"r",function(){return Z}),n.d(t,"k",function(){return x}),n.d(t,"o",function(){return H}),n.d(t,"l",function(){return O}),n.d(t,"f",function(){return T}),n.d(t,"a",function(){return k}),n.d(t,"b",function(){return C});var i=n(31),a=n(80),r=n(1),o=n(5),s=n(47),l=n(35),c=n(0),u=n(12),p=n(3),m=n(9),d=n(21),h=n(14),g=n(40),_=n(16),y=n(29),f=n(2),b=n(27),E=n(23),v=n(22),k={LimitBuy:"Limit Buy Order",LimitSell:"Limit Sell Order",StopBuy:"Stop Buy Order",StopSell:"Stop Sell Order"},C={Long:"L",Short:"S"};function O(e,t,n,a,r,o=null){var s=o instanceof l.b,c=new M(e,t,n,a,r);if(isNaN(t)||isNaN(n))return s?o.scriptRef.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument"):Object(m.dialogBoxCreate)("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument"),!1;if(null==P.Orders){var u={};for(var p in P)if(P.hasOwnProperty(p)){if(!((e=P[p])instanceof i.Stock))continue;u[e.symbol]=[]}P.Orders=u}return P.Orders[e.symbol].push(c),U(c.stock,c.type,c.pos),ee(c.stock),!0}function T(e,t=null){var n=t instanceof l.b;if(null==P.Orders)return!1;if(e.order&&e.order instanceof M){for(var a=e.order,r=P.Orders[a.stock.symbol],o=0;oe.maxShares)return Object(m.dialogBoxCreate)(`You cannot purchase this many shares. ${e.symbol} has a maximum of `+`${p.numeralWrapper.formatBigNumber(e.maxShares)} shares.`),!1;var i=e.playerShares*e.playerAvgPx;c.a.loseMoney(n+r.CONSTANTS.StockMarketCommission);var a=i+n;return e.playerShares=Math.round(e.playerShares+t),e.playerAvgPx=a/e.playerShares,Z(e),Object(m.dialogBoxCreate)("Bought "+p.numeralWrapper.format(t,"0,0")+" shares of "+e.symbol+" at "+p.numeralWrapper.format(e.price,"($0.000a)")+" per share. Paid "+p.numeralWrapper.format(r.CONSTANTS.StockMarketCommission,"($0.000a)")+" in commission fees."),!0}function B(e,t){if(0==t)return!1;if(null==e||t<0||isNaN(t))return Object(m.dialogBoxCreate)("Failed to sell stock. This may be a bug, contact developer"),!1;if((t=Math.round(t))>e.playerShares&&(t=e.playerShares),0===t)return!1;var n=e.price*t-r.CONSTANTS.StockMarketCommission;return c.a.gainMoney(n),c.a.recordMoneySource(n,"stock"),e.playerShares=Math.round(e.playerShares-t),0==e.playerShares&&(e.playerAvgPx=0),Z(e),Object(m.dialogBoxCreate)("Sold "+p.numeralWrapper.format(t,"0,0")+" shares of "+e.symbol+" at "+p.numeralWrapper.format(e.price,"($0.000a)")+" per share. After commissions, you gained a total of "+p.numeralWrapper.format(n,"($0.000a)")+"."),!0}function j(e,t,n=null){var i=n instanceof l.b;if(0===(t=Math.round(t))||t<0)return!1;if(null==e||isNaN(t))return i?n.scriptRef.log("ERROR: shortStock() failed because of invalid arguments."):Object(m.dialogBoxCreate)("Failed to initiate a short position in a stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, so contact developer"),!1;var a=e.price*t;if(c.a.money.lt(a+r.CONSTANTS.StockMarketCommission))return i?n.scriptRef.log("ERROR: shortStock() failed because you do not have enough money to purchase this short position. You need "+p.numeralWrapper.format(a+r.CONSTANTS.StockMarketCommission,"($0.000a)")+"."):Object(m.dialogBoxCreate)("You do not have enough money to purchase this short position. You need "+p.numeralWrapper.format(a+r.CONSTANTS.StockMarketCommission,"($0.000a)")+"."),!1;if(t+e.playerShares+e.playerShortShares>e.maxShares)return i?n.scriptRef.log("ERROR: shortStock() failed because purchasing this many short shares would exceed "+`${e.symbol}'s maximum number of shares.`):Object(m.dialogBoxCreate)(`You cannot purchase this many shares. ${e.symbol} has a maximum of `+`${e.maxShares} shares.`),!1;var o=e.playerShortShares*e.playerAvgShortPx;c.a.loseMoney(a+r.CONSTANTS.StockMarketCommission);var s=o+a;return e.playerShortShares=Math.round(e.playerShortShares+t),e.playerAvgShortPx=s/e.playerShortShares,Z(e),i?null==n.disableLogs.ALL&&null==n.disableLogs.shortStock&&n.scriptRef.log("Bought a short position of "+p.numeralWrapper.format(t,"0,0")+" shares of "+e.symbol+" at "+p.numeralWrapper.format(e.price,"($0.000a)")+" per share. Paid "+p.numeralWrapper.format(r.CONSTANTS.StockMarketCommission,"($0.000a)")+" in commission fees."):Object(m.dialogBoxCreate)("Bought a short position of "+p.numeralWrapper.format(t,"0,0")+" shares of "+e.symbol+" at "+p.numeralWrapper.format(e.price,"($0.000a)")+" per share. Paid "+p.numeralWrapper.format(r.CONSTANTS.StockMarketCommission,"($0.000a)")+" in commission fees."),!0}function W(e,t,n=null){var i=n instanceof l.b;if(null==e||isNaN(t)||t<0)return i?n.scriptRef.log("ERROR: sellShort() failed because of invalid arguments."):Object(m.dialogBoxCreate)("Failed to sell a short position in a stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, so contact developer"),!1;if((t=Math.round(t))>e.playerShortShares&&(t=e.playerShortShares),0===t)return!1;var a=t*e.playerAvgShortPx,o=(e.playerAvgShortPx-e.price)*t-r.CONSTANTS.StockMarketCommission;return isNaN(o)&&(o=0),c.a.gainMoney(a+o),c.a.recordMoneySource(o,"stock"),i&&(n.scriptRef.onlineMoneyMade+=o,c.a.scriptProdSinceLastAug+=o),e.playerShortShares=Math.round(e.playerShortShares-t),0===e.playerShortShares&&(e.playerAvgShortPx=0),Z(e),i?null==n.disableLogs.ALL&&null==n.disableLogs.sellShort&&n.scriptRef.log("Sold your short position of "+p.numeralWrapper.format(t,"0,0")+" shares of "+e.symbol+" at "+p.numeralWrapper.format(e.price,"($0.000a)")+" per share. After commissions, you gained a total of "+p.numeralWrapper.format(a+o,"($0.000a)")+"."):Object(m.dialogBoxCreate)("Sold your short position of "+p.numeralWrapper.format(t,"0,0")+" shares of "+e.symbol+" at "+p.numeralWrapper.format(e.price,"($0.000a)")+" per share. After commissions, you gained a total of "+p.numeralWrapper.format(a+o,"($0.000a)")+"."),!0}function F(e=1){(null==P.storedCycles||isNaN(P.storedCycles))&&(P.storedCycles=0),P.storedCycles+=e;const t=6e3/r.CONSTANTS.MilliPerCycle;if(P.storedCycles=s.cap&&(c=.1,s.b=!1);var p=Math.random();p50&&(s.otlkMag=50),s.otlkMag<0&&(s.otlkMag*=-1,s.b=!s.b)}}}function U(e,t,n){var a=P.Orders;if(null!=a){var r=a[e.symbol];if(null==r||r.constructor!==Array)return console.log("ERROR: Invalid Order book for "+e.symbol+" in processOrders()"),void(r=[]);for(var o=0;o=s.price&&S(s);break;case k.LimitSell:case k.StopBuy:s.pos===C.Long&&e.price>=s.price?S(s):s.pos===C.Short&&e.price<=s.price&&S(s);break;case k.StopSell:s.pos===C.Long&&e.price<=s.price?S(s):s.pos===C.Short&&e.price>=s.price&&S(s);break;default:return void console.log("Invalid order type: "+s.type)}}}else{var l={};for(var c in P)if(P.hasOwnProperty(c)){if(!((e=P[c])instanceof i.Stock))continue;l[e.symbol]=[]}P.Orders=l}}function H(e){G=e}var G=!1,K=!1,q=r.CONSTANTS.StockMarketCommission;function $(){function e(e,t,n,i,a){e.innerText=i,e.classList.remove("a-link-button"),e.classList.remove("a-link-button-bought"),e.classList.remove("a-link-button-inactive"),!n&&c.a.money.gte(t)?e.classList.add("a-link-button"):n?(e.innerText=a,e.classList.add("a-link-button-bought")):e.classList.add("a-link-button-inactive")}null==c.a.hasWseAccount&&(c.a.hasWseAccount=!1),null==c.a.hasTixApiAccess&&(c.a.hasTixApiAccess=!1),null==c.a.has4SData&&(c.a.has4SData=!1),null==c.a.has4SDataTixApi&&(c.a.has4SDataTixApi=!1);var t=Object(d.clearEventListeners)("stock-market-buy-account");e(t,r.CONSTANTS.WSEAccountCost,c.a.hasWseAccount,"Buy WSE Account - "+p.numeralWrapper.format(r.CONSTANTS.WSEAccountCost,"($0.000a)"),"WSE Account - Purchased"),t.addEventListener("click",function(){return c.a.hasWseAccount=!0,N(),I(),c.a.loseMoney(r.CONSTANTS.WSEAccountCost),$(),!1});var n=Object(d.clearEventListeners)("stock-market-buy-tix-api");e(n,r.CONSTANTS.TIXAPICost,c.a.hasTixApiAccess,"Buy Trade Information eXchange (TIX) API Access - "+p.numeralWrapper.format(r.CONSTANTS.TIXAPICost,"($0.000a)"),"TIX API Access - Purchased"),n.addEventListener("click",function(){return c.a.hasTixApiAccess=!0,c.a.loseMoney(r.CONSTANTS.TIXAPICost),$(),!1});var o=Object(d.clearEventListeners)("stock-market-buy-4s-data");e(o,Object(a.getStockMarket4SDataCost)(),c.a.has4SData,"Buy 4S Market Data Access - "+p.numeralWrapper.format(Object(a.getStockMarket4SDataCost)(),"($0.000a)"),"4S Market Data - Purchased"),o.addEventListener("click",function(){return!c.a.money.lt(Object(a.getStockMarket4SDataCost)())&&(c.a.has4SData=!0,c.a.loseMoney(Object(a.getStockMarket4SDataCost)()),$(),!1)}),o.appendChild(Object(f.createElement)("span",{class:"tooltiptext",innerText:"Lets you view additional pricing and volatility information about stocks"})),o.style.marginRight="2px";var s=Object(d.clearEventListeners)("stock-market-4s-data-help-tip");s.style.marginTop="10px",s.addEventListener("click",()=>(Object(m.dialogBoxCreate)("Access to the 4S Market Data feed will display two additional pieces of information about each stock: Price Forecast & Volatility

Price Forecast indicates the probability the stock has of increasing or decreasing. A '+' forecast means the stock has a higher chance of increasing than decreasing, and a '-' means the opposite. The number of '+/-' symbols is used to illustrate the magnitude of these probabilities. For example, '+++' means that the stock has a significantly higher chance of increasing than decreasing, while '+' means that the stock only has a slightly higher chance of increasing than decreasing.

Volatility represents the maximum percentage by which a stock's price can change every tick (a tick occurs every few seconds while the game is running).

A stock's price forecast can change over time. This is also affected by volatility. The more volatile a stock is, the more its price forecast will change."),!1));var l=Object(d.clearEventListeners)("stock-market-buy-4s-tix-api");if(e(l,Object(a.getStockMarket4STixApiCost)(),c.a.has4SDataTixApi,"Buy 4S Market Data TIX API Access - "+p.numeralWrapper.format(Object(a.getStockMarket4STixApiCost)(),"($0.000a)"),"4S Market Data TIX API - Purchased"),c.a.hasTixApiAccess?(l.addEventListener("click",function(){return!c.a.money.lt(Object(a.getStockMarket4STixApiCost)())&&(c.a.has4SDataTixApi=!0,c.a.loseMoney(Object(a.getStockMarket4STixApiCost)()),$(),!1)}),l.appendChild(Object(f.createElement)("span",{class:"tooltiptext",innerText:"Lets you access 4S Market Data through Netscript"}))):(l.classList.remove("a-link-button"),l.classList.remove("a-link-button-bought"),l.classList.remove("a-link-button-inactive"),l.classList.add("a-link-button-inactive"),l.appendChild(Object(f.createElement)("span",{class:"tooltiptext",innerText:"Requires TIX API Access"}))),null!=(v=document.getElementById("stock-market-list"))){var u=document.getElementById("stock-market-commission"),h=document.getElementById("stock-market-mode"),g=document.getElementById("stock-market-expand-tickers"),_=document.getElementById("stock-market-collapse-tickers"),b=document.getElementById("stock-market-watchlist-filter"),E=document.getElementById("stock-market-watchlist-filter-update");if(!c.a.hasWseAccount){for(G=!1;v.firstChild;)v.removeChild(v.firstChild);return u.style.visibility="hidden",h.style.visibility="hidden",g.style.visibility="hidden",_.style.visibility="hidden",b.style.visibility="hidden",void(E.style.visibility="hidden")}if(u.style.visibility="visible",h.style.visibility="visible",g.style.visibility="visible",_.style.visibility="visible",b.style.visibility="visible",E.style.visibility="visible",!G&&c.a.hasWseAccount){console.log("Creating Stock Market UI"),u.innerHTML="Commission Fees: Every transaction you make has a "+p.numeralWrapper.format(r.CONSTANTS.StockMarketCommission,"($0.000a)")+" commission fee.

WARNING: When you reset after installing Augmentations, the Stock Market is reset. This means all your positions are lost, so make sure to sell your stocks before installing Augmentations!",Object(d.clearEventListeners)("stock-market-investopedia").addEventListener("click",function(){return Object(m.dialogBoxCreate)("When making a transaction on the stock market, there are two types of positions: Long and Short. A Long position is the typical scenario where you buy a stock and earn a profit if the price of that stock increases. Meanwhile, a Short position is the exact opposite. In a Short position you purchase shares of a stock and earn a profit if the price of that stock decreases. This is also called 'shorting' a stock.

NOTE: Shorting stocks is not available immediately, and must be unlocked later on in the game.

There are three different types of orders you can make to buy or sell stocks on the exchange: Market Order, Limit Order, and Stop Order. Note that Limit Orders and Stop Orders are not available immediately, and must be unlocked later on in the game.

When you place a Market Order to buy or sell a stock, the order executes immediately at whatever the current price of the stock is. For example if you choose to short a stock with 5000 shares using a Market Order, you immediately purchase those 5000 shares in a Short position at whatever the current market price is for that stock.

A Limit Order is an order that only executes under certain conditions. A Limit Order is used to buy or sell a stock at a specified price or better. For example, lets say you purchased a Long position of 100 shares of some stock at a price of $10 per share. You can place a Limit Order to sell those 100 shares at $50 or better. The Limit Order will execute when the price of the stock reaches a value of $50 or higher.

A Stop Order is the opposite of a Limit Order. It is used to buy or sell a stock at a specified price (before the price gets 'worse'). For example, lets say you purchased a Short position of 100 shares of some stock at a price of $100 per share. The current price of the stock is $80 (a profit of $20 per share). You can place a Stop Order to sell the Short position if the stock's price reaches $90 or higher. This can be used to lock in your profits and limit any losses.

Here is a summary of how each order works and when they execute:

In a LONG Position:

A Limit Order to buy will execute if the stock's price <= order's price
A Limit Order to sell will execute if the stock's price >= order's price
A Stop Order to buy will execute if the stock's price >= order's price
A Stop Order to sell will execute if the stock's price <= order's price

In a SHORT Position:

A Limit Order to buy will execute if the stock's price >= order's price
A Limit Order to sell will execute if the stock's price <= order's price
A Stop Order to buy will execute if the stock's price <= order's price
A Stop Order to sell will execute if the stock's price >= order's price."),!1}),h&&(K=!1,h.innerHTML="Switch to 'Portfolio' ModeDisplays only the stocks for which you have shares or orders",h.addEventListener("click",Y));var v=document.getElementById("stock-market-list");g&&g.addEventListener("click",()=>{for(var e=v.getElementsByClassName("accordion-header"),t=0;t{for(var e=v.getElementsByClassName("accordion-header"),t=0;t{let e=b.value.toString();P.watchlistFilter=e.replace(/\s/g,""),K?Y():z()}),b.addEventListener("keyup",e=>{e.preventDefault(),e.keyCode===y.KEY.ENTER&&E.click()})):console.warn("Stock Market Watchlist DOM elements could not be found"),V(),G=!0}if(c.a.hasWseAccount)for(var k in P)if(P.hasOwnProperty(k)){var C=P[k];C instanceof i.Stock&&(Q(C,null),ee(C))}}}function Y(){K=!0;var e=Object(d.clearEventListeners)("stock-market-mode");e&&(e.innerHTML="Switch to 'All stocks' ModeDisplays all stocks on the WSE",e.addEventListener("click",z)),V()}function z(){K=!1;var e=Object(d.clearEventListeners)("stock-market-mode");e&&(e.innerHTML="Switch to 'Portfolio' ModeDisplays only the stocks for which you have shares or orders",e.addEventListener("click",Y)),V()}function V(){var e=document.getElementById("stock-market-list");null==e&&Object(g.exceptionAlert)("Error creating Stock Tickers UI. DOM element with ID 'stock-market-list' could not be found"),Object(b.removeChildrenFromElement)(e);var t=P.Orders;if(null==t){var n={};for(var a in P)if(P.hasOwnProperty(a)){if(!((o=P[a])instanceof i.Stock))continue;n[o.symbol]=[]}P.Orders=n,t=P.Orders}let r=null;if(null!=P.watchlistFilter&&""!==P.watchlistFilter){r=P.watchlistFilter.replace(/\s/g,"").split(",")}for(var a in P)if(P.hasOwnProperty(a)){var o;if(!((o=P[a])instanceof i.Stock))continue;if(r&&!r.includes(o.symbol))continue;let e=t[o.symbol];if(K&&0===o.playerShares&&0===o.playerShortShares&&0===e.length)continue;J(o)}X()}function J(e){if(e instanceof i.Stock){var t="stock-market-ticker-"+e.symbol,n=document.createElement("li"),a=document.createElement("button");a.classList.add("accordion-header"),a.setAttribute("id",t+"-hdr"),a.innerHTML=e.name+" - "+e.symbol+" - "+p.numeralWrapper.format(e.price,"($0.000a)");var r=document.createElement("div");r.classList.add("accordion-panel"),r.setAttribute("id",t+"-panel");var o=document.createElement("input"),l=document.createElement("select"),u=document.createElement("select"),d=document.createElement("span"),h=document.createElement("span"),g=document.createElement("span"),_=document.createElement("span"),y=document.createElement("p"),f=document.createElement("ul");o.classList.add("stock-market-input"),o.placeholder="Quantity (Shares)",o.setAttribute("id",t+"-qty-input"),o.setAttribute("onkeydown","return ( event.ctrlKey || event.altKey || (4734 && event.keyCode<40) || (event.keyCode==46) )"),l.classList.add("stock-market-input"),l.setAttribute("id",t+"-pos-selector");var b=document.createElement("option");if(b.text="Long",l.add(b),8===c.a.bitNodeN||s.f&&s.h>=2){var E=document.createElement("option");E.text="Short",l.add(E)}u.classList.add("stock-market-input"),u.setAttribute("id",t+"-order-selector");var T=document.createElement("option");if(T.text="Market Order",u.add(T),8===c.a.bitNodeN||s.f&&s.h>=3){var S=document.createElement("option");S.text="Limit Order",u.add(S);var M=document.createElement("option");M.text="Stop Order",u.add(M)}d.classList.add("stock-market-input"),d.classList.add("a-link-button"),d.innerHTML="Buy",d.addEventListener("click",()=>{var n=l.options[l.selectedIndex].text;n="Long"===n?C.Long:C.Short;var i=u.options[u.selectedIndex].text,a=Number(document.getElementById(t+"-qty-input").value);if(isNaN(a))return!1;switch(i){case"Market Order":n===C.Long?D(e,a):j(e,a,null);break;case"Limit Order":case"Stop Order":var r=Object(v.yesNoTxtInpBoxGetYesButton)(),o=Object(v.yesNoTxtInpBoxGetNoButton)();r.innerText="Place Buy "+i,o.innerText="Cancel Order",r.addEventListener("click",()=>{var t,r=Number(Object(v.yesNoTxtInpBoxGetInput)());t="Limit Order"===i?k.LimitBuy:k.StopBuy,O(e,a,r,t,n),Object(v.yesNoTxtInpBoxClose)()}),o.addEventListener("click",()=>{Object(v.yesNoTxtInpBoxClose)()}),Object(v.yesNoTxtInpBoxCreate)("Enter the price for your "+i);break;default:console.log("ERROR: Invalid order type")}return!1}),h.classList.add("stock-market-input"),h.classList.add("a-link-button"),h.innerHTML="Sell",h.addEventListener("click",()=>{var n=l.options[l.selectedIndex].text;n="Long"===n?C.Long:C.Short;var i=u.options[u.selectedIndex].text,a=Number(document.getElementById(t+"-qty-input").value);if(isNaN(a))return!1;switch(i){case"Market Order":n===C.Long?B(e,a):W(e,a,null);break;case"Limit Order":case"Stop Order":var r=Object(v.yesNoTxtInpBoxGetYesButton)(),o=Object(v.yesNoTxtInpBoxGetNoButton)();r.innerText="Place Sell "+i,o.innerText="Cancel Order",r.addEventListener("click",()=>{var t,r=Number(Object(v.yesNoTxtInpBoxGetInput)());t="Limit Order"===i?k.LimitSell:k.StopSell,Object(v.yesNoTxtInpBoxClose)(),O(e,a,r,t,n)}),o.addEventListener("click",()=>{Object(v.yesNoTxtInpBoxClose)()}),Object(v.yesNoTxtInpBoxCreate)("Enter the price for your "+i);break;default:console.log("ERROR: Invalid order type")}return!1}),g.classList.add("stock-market-input"),g.classList.add("a-link-button"),g.innerHTML="Buy MAX",g.addEventListener("click",()=>{var t=l.options[l.selectedIndex].text;t="Long"===t?C.Long:C.Short;var n=u.options[u.selectedIndex].text,i=c.a.money.toNumber();switch(n){case"Market Order":var a=Math.floor((i-q)/e.price);a=Math.min(a,Math.round(e.maxShares-e.playerShares-e.playerShortShares)),t===C.Long?D(e,a):j(e,a,null);break;case"Limit Order":case"Stop Order":var r=Object(v.yesNoTxtInpBoxGetYesButton)(),o=Object(v.yesNoTxtInpBoxGetNoButton)();r.innerText="Place Buy "+n,o.innerText="Cancel Order",r.addEventListener("click",()=>{var a,r=Number(Object(v.yesNoTxtInpBoxGetInput)());a="Limit Order"===n?k.LimitBuy:k.StopBuy;var o=Math.floor((i-q)/r);o=Math.min(o,Math.round(e.maxShares-e.playerShares-e.playerShortShares)),O(e,o,r,a,t),Object(v.yesNoTxtInpBoxClose)()}),o.addEventListener("click",()=>{Object(v.yesNoTxtInpBoxClose)()}),Object(v.yesNoTxtInpBoxCreate)("Enter the price for your "+n);break;default:console.log("ERROR: Invalid order type")}return!1}),_.classList.add("stock-market-input"),_.classList.add("a-link-button"),_.innerHTML="Sell ALL",_.addEventListener("click",()=>{var t=l.options[l.selectedIndex].text;switch(t="Long"===t?C.Long:C.Short,u.options[u.selectedIndex].text){case"Market Order":if(t===C.Long){var n=e.playerShares;B(e,n)}else{n=e.playerShortShares;W(e,n,null)}break;case"Limit Order":case"Stop Order":Object(m.dialogBoxCreate)("ERROR: 'Sell All' only works for Market Orders");break;default:console.log("ERROR: Invalid order type")}return!1}),y.setAttribute("id",t+"-position-text"),y.classList.add("stock-market-position-text"),e.posTxtEl=y,f.setAttribute("id",t+"-order-list"),f.classList.add("stock-market-order-list"),r.appendChild(o),r.appendChild(l),r.appendChild(u),r.appendChild(d),r.appendChild(h),r.appendChild(g),r.appendChild(_),r.appendChild(y),r.appendChild(f),n.appendChild(a),n.appendChild(r),document.getElementById("stock-market-list").appendChild(n),Q(e,!0),Z(e),ee(e)}else console.log("Invalid stock in createStockSticker()")}function X(){var e=document.getElementById("stock-market-list").getElementsByClassName("accordion-header");if(null!=e)for(var t=0;t0||e.playerShortShares>0)&&Z(e);var a=document.getElementById(n+"-hdr");if(null==a){if(!K){let t=P.watchlistFilter;""!==t&&t.includes(e.symbol)&&console.log("ERROR: Couldn't find ticker element for stock: "+e.symbol)}return}let r=e.name+" ("+e.symbol+") - "+p.numeralWrapper.format(e.price,"($0.000a)");c.a.has4SData&&(r+=" - Volatility: "+p.numeralWrapper.format(e.mv,"0,0.00")+"% - Price Forecast: ",e.b?r+="+".repeat(Math.floor(e.otlkMag/10)+1):r+="-".repeat(Math.floor(e.otlkMag/10)+1)),a.innerText=r,null!=t&&(a.style.color=t?"#66ff33":"red")}function Z(e){if(!u.routing.isOn(u.Page.StockMarket))return;if(!(e instanceof i.Stock))return console.log("Invalid stock in updateStockPlayerPosition():"),void console.log(e);var t="stock-market-ticker-"+e.symbol;if(K){if(0===e.playerShares&&0===e.playerShortShares&&P.Orders&&P.Orders[e.symbol]&&0===P.Orders[e.symbol].length)return Object(E.removeElementById)(t+"-hdr"),void Object(E.removeElementById)(t+"-panel");if(null==document.getElementById(t+"-hdr"))return J(e),void X()}if(e.posTxtEl instanceof Element||(e.posTxtEl=document.getElementById(t+"-position-text")),null==e.posTxtEl)return void console.log("ERROR: Could not find stock position element for: "+e.symbol);const n=e.playerShares*e.playerAvgPx;let a=(e.price-e.playerAvgPx)*e.playerShares,r=a/n;isNaN(r)&&(r=0);const o=e.playerShortShares*e.playerAvgShortPx;let l=(e.playerAvgShortPx-e.price)*e.playerShortShares,m=l/o;isNaN(m)&&(m=0),e.posTxtEl.innerHTML=`Max Shares: ${p.numeralWrapper.format(e.maxShares,"0.000a")}
`+"

Long Position: Shares in the long position will increase in value if the price of the corresponding stock increases


Shares: "+p.numeralWrapper.format(e.playerShares,"0,0")+"
Average Price: "+p.numeralWrapper.format(e.playerAvgPx,"$0.000a")+" (Total Cost: "+p.numeralWrapper.format(n,"$0.000a")+")
Profit: "+p.numeralWrapper.format(a,"$0.000a")+" ("+p.numeralWrapper.format(r,"0.00%")+")
",(8===c.a.bitNodeN||s.f&&s.h>=2)&&(e.posTxtEl.innerHTML+="

Short Position: Shares in short position will increase in value if the price of the corresponding stock decreases


Shares: "+p.numeralWrapper.format(e.playerShortShares,"0,0")+"
Average Price: "+p.numeralWrapper.formatMoney(e.playerAvgShortPx)+" (Total Cost: "+p.numeralWrapper.formatMoney(o)+")
Profit: "+p.numeralWrapper.formatMoney(l)+" ("+p.numeralWrapper.format(m,"0.00%")+")

Orders:

")}function ee(e){if(u.routing.isOn(u.Page.StockMarket)){var t="stock-market-ticker-"+e.symbol,n=document.getElementById(t+"-order-list");if(null!=n){var i=P.Orders;if(null!=i){var a=i[e.symbol];if(null!=a){if(K){if(0===e.playerShares&&0===e.playerShortShares&&P.Orders&&P.Orders[e.symbol]&&0===P.Orders[e.symbol].length)return Object(E.removeElementById)(t+"-hdr"),void Object(E.removeElementById)(t+"-panel");if(null==document.getElementById(t+"-hdr"))return J(e),void X()}for(;n.firstChild;)n.removeChild(n.firstChild);for(var r=0;r
Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Energy,"$0.000a")+"
Recommended starting Industry: NO",Utilities:"Distributes water and provides wastewater services.

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Utilities,"$0.000a")+"
Recommended starting Industry: NO",Agriculture:"Cultive crops and breed livestock to produce food.

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Agriculture,"$0.000a")+"
Recommended starting Industry: YES",Fishing:"Produce food through the breeding and processing of fish and fish products

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Fishing,"$0.000a")+"
Recommended starting Industry: NO",Mining:"Extract and process metals from the earth.

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Mining,"$0.000a")+"
Recommended starting Industry: NO",Food:"Create your own restaurants all around the world.

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Food,"$0.000a")+"
Recommended starting Industry: YES",Tobacco:"Create and distribute tobacco and tobacco-related products.

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Tobacco,"$0.000a")+"
Recommended starting Industry: YES",Chemical:"Product industrial chemicals

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Chemical,"$0.000a")+"
Recommended starting Industry: NO",Pharmaceutical:"Discover, develop, and create new pharmaceutical drugs.

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Pharmaceutical,"$0.000a")+"
Recommended starting Industry: NO",Computer:"Develop and manufacture new computer hardware and networking infrastructures.

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Computer,"$0.000a")+"
Recommended starting Industry: NO",Robotics:"Develop and create robots.

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Robotics,"$0.000a")+"
Recommended starting Industry: NO",Software:"Develop computer software and create AI Cores.

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Software,"$0.000a")+"
Recommended starting Industry: YES",Healthcare:"Create and manage hospitals.

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Healthcare,"$0.000a")+"
Recommended starting Industry: NO",RealEstate:"Develop and manage real estate properties.

Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.RealEstate,"$0.000a")+"
Recommended starting Industry: NO"},t.IndustryResearchTrees={Energy:i.getBaseResearchTreeCopy(),Utilities:i.getBaseResearchTreeCopy(),Agriculture:i.getBaseResearchTreeCopy(),Fishing:i.getBaseResearchTreeCopy(),Mining:i.getBaseResearchTreeCopy(),Food:i.getBaseResearchTreeCopy(),Tobacco:i.getBaseResearchTreeCopy(),Chemical:i.getBaseResearchTreeCopy(),Pharmaceutical:i.getBaseResearchTreeCopy(),Computer:i.getBaseResearchTreeCopy(),Robotics:i.getBaseResearchTreeCopy(),Software:i.getBaseResearchTreeCopy(),Healthcare:i.getBaseResearchTreeCopy(),RealEstate:i.getBaseResearchTreeCopy()},t.resetIndustryResearchTrees=function(){t.IndustryResearchTrees.Energy=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Utilities=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Agriculture=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Fishing=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Mining=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Food=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Tobacco=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Chemical=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Pharmaceutical=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Computer=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Robotics=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Software=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Healthcare=i.getBaseResearchTreeCopy(),t.IndustryResearchTrees.RealEstate=i.getBaseResearchTreeCopy()}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(21);t.MainMenuLinks={Terminal:null,ScriptEditor:null,ActiveScripts:null,CreateProgram:null,Stats:null,Factions:null,Augmentations:null,HacknetNodes:null,Sleeves:null,City:null,Travel:null,Job:null,StockMarket:null,Bladeburner:null,Corporation:null,Gang:null,Tutorial:null,Options:null,DevMenu:null},t.initializeMainMenuLinks=function(){try{function e(e){const t=i.clearEventListeners(e);if(null==t)throw new Error(`clearEventListeners() failed for element with id: ${e}`);return t}return t.MainMenuLinks.Terminal=e("terminal-menu-link"),t.MainMenuLinks.ScriptEditor=e("create-script-menu-link"),t.MainMenuLinks.ActiveScripts=e("active-scripts-menu-link"),t.MainMenuLinks.CreateProgram=e("create-program-menu-link"),t.MainMenuLinks.Stats=e("stats-menu-link"),t.MainMenuLinks.Factions=e("factions-menu-link"),t.MainMenuLinks.Augmentations=e("augmentations-menu-link"),t.MainMenuLinks.HacknetNodes=e("hacknet-nodes-menu-link"),t.MainMenuLinks.Sleeves=e("sleeves-menu-link"),t.MainMenuLinks.City=e("city-menu-link"),t.MainMenuLinks.Travel=e("travel-menu-link"),t.MainMenuLinks.Job=e("job-menu-link"),t.MainMenuLinks.StockMarket=e("stock-market-menu-link"),t.MainMenuLinks.Bladeburner=e("bladeburner-menu-link"),t.MainMenuLinks.Corporation=e("corporation-menu-link"),t.MainMenuLinks.Gang=e("gang-menu-link"),t.MainMenuLinks.Tutorial=e("tutorial-menu-link"),t.MainMenuLinks.Options=document.getElementById("options-menu-link"),t.MainMenuLinks.DevMenu=e("dev-menu-link"),!0}catch(e){return console.error(`Failed to initialize Main Menu Links: ${e}`),!1}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(48),a=n(62);t.removeChildrenFromElement=function(e){if(null!==e)try{const t=i.isString(e)?a.getElementById(e):e;if(t instanceof Element)for(;null!==t.firstChild;)t.removeChild(t.firstChild)}catch(e){return void console.debug(e)}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(244),a=n(243);t.Programs={};for(const e of a.programsMetadata)t.Programs[e.key]=new i.Program(e.name,e.create)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.KEY={A:65,B:66,C:67,CTRL:17,D:68,DOWNARROW:40,E:69,ENTER:13,ESC:27,F:70,H:72,J:74,K:75,L:76,M:77,N:78,O:79,P:80,R:82,S:83,TAB:9,U:85,UPARROW:38,W:87,1:49,2:50}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.EmployeePositions={Operations:"Operations",Engineer:"Engineer",Business:"Business",Management:"Management",RandD:"Research & Development",Training:"Training",Unassigned:"Unassigned"}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(14),a=n(16);class r{static fromJSON(e){return i.Generic_fromJSON(r,e.data)}constructor(e="",t="",n=1,i=!0,r=0,o=1e4,s=1e12){this.name=e,this.symbol=t,this.price=o,this.playerShares=0,this.playerAvgPx=0,this.playerShortShares=0,this.playerAvgShortPx=0,this.mv=n,this.b=i,this.otlkMag=r,this.cap=a.getRandomInt(1e3*o,25e3*o);let l=s/o;this.totalShares=1e5*Math.round(l/1e5);this.maxShares=1e5*Math.round(.2*this.totalShares/1e5),this.posTxtEl=null}toJSON(){return i.Generic_toJSON("Stock",this)}}t.Stock=r,i.Reviver.constructors.Stock=r},function(module,__webpack_exports__,__webpack_require__){"use strict";(function($){__webpack_require__.d(__webpack_exports__,"d",function(){return getCurrentEditor}),__webpack_require__.d(__webpack_exports__,"h",function(){return updateScriptEditorContent}),__webpack_require__.d(__webpack_exports__,"f",function(){return loadAllRunningScripts}),__webpack_require__.d(__webpack_exports__,"c",function(){return findRunningScript}),__webpack_require__.d(__webpack_exports__,"a",function(){return RunningScript}),__webpack_require__.d(__webpack_exports__,"b",function(){return Script}),__webpack_require__.d(__webpack_exports__,"g",function(){return scriptEditorInit}),__webpack_require__.d(__webpack_exports__,"e",function(){return isScriptFilename});var _Constants__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(1),_Constants__WEBPACK_IMPORTED_MODULE_0___default=__webpack_require__.n(_Constants__WEBPACK_IMPORTED_MODULE_0__),_engine__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(10),_Fconf__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(52),_InteractiveTutorial__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(38),_NetscriptEvaluator__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__(7),_NetscriptFunctions__WEBPACK_IMPORTED_MODULE_5__=__webpack_require__(47),_NetscriptWorker__WEBPACK_IMPORTED_MODULE_6__=__webpack_require__(35),_Player__WEBPACK_IMPORTED_MODULE_7__=__webpack_require__(0),_ScriptEditor_Ace__WEBPACK_IMPORTED_MODULE_8__=__webpack_require__(96),_ScriptEditor_CodeMirror__WEBPACK_IMPORTED_MODULE_9__=__webpack_require__(95),_Server__WEBPACK_IMPORTED_MODULE_10__=__webpack_require__(11),_Settings_Settings__WEBPACK_IMPORTED_MODULE_11__=__webpack_require__(18),_Settings_Settings__WEBPACK_IMPORTED_MODULE_11___default=__webpack_require__.n(_Settings_Settings__WEBPACK_IMPORTED_MODULE_11__),_Settings_SettingEnums__WEBPACK_IMPORTED_MODULE_12__=__webpack_require__(43),_Settings_SettingEnums__WEBPACK_IMPORTED_MODULE_12___default=__webpack_require__.n(_Settings_SettingEnums__WEBPACK_IMPORTED_MODULE_12__),_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_13__=__webpack_require__(8),_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_13___default=__webpack_require__.n(_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_13__),_TextFile__WEBPACK_IMPORTED_MODULE_14__=__webpack_require__(75),_TextFile__WEBPACK_IMPORTED_MODULE_14___default=__webpack_require__.n(_TextFile__WEBPACK_IMPORTED_MODULE_14__),_utils_acorn__WEBPACK_IMPORTED_MODULE_15__=__webpack_require__(55),_utils_acorn__WEBPACK_IMPORTED_MODULE_15___default=__webpack_require__.n(_utils_acorn__WEBPACK_IMPORTED_MODULE_15__),_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_16__=__webpack_require__(12),_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_16___default=__webpack_require__.n(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_16__),_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_17__=__webpack_require__(3),_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_17___default=__webpack_require__.n(_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_17__),_utils_SetTimeoutRef__WEBPACK_IMPORTED_MODULE_18__=__webpack_require__(56),_utils_SetTimeoutRef__WEBPACK_IMPORTED_MODULE_18___default=__webpack_require__.n(_utils_SetTimeoutRef__WEBPACK_IMPORTED_MODULE_18__),_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__=__webpack_require__(9),_utils_JSONReviver__WEBPACK_IMPORTED_MODULE_20__=__webpack_require__(14),_utils_helpers_compareArrays__WEBPACK_IMPORTED_MODULE_21__=__webpack_require__(111),_utils_helpers_compareArrays__WEBPACK_IMPORTED_MODULE_21___default=__webpack_require__.n(_utils_helpers_compareArrays__WEBPACK_IMPORTED_MODULE_21__),_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__=__webpack_require__(2),_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22___default=__webpack_require__.n(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__),_utils_helpers_getTimestamp__WEBPACK_IMPORTED_MODULE_23__=__webpack_require__(105),_utils_helpers_getTimestamp__WEBPACK_IMPORTED_MODULE_23___default=__webpack_require__.n(_utils_helpers_getTimestamp__WEBPACK_IMPORTED_MODULE_23__),_utils_helpers_roundToTwo__WEBPACK_IMPORTED_MODULE_24__=__webpack_require__(99),_utils_helpers_roundToTwo__WEBPACK_IMPORTED_MODULE_24___default=__webpack_require__.n(_utils_helpers_roundToTwo__WEBPACK_IMPORTED_MODULE_24__);const walk=__webpack_require__(181);function isScriptFilename(e){return e.endsWith(".js")||e.endsWith(".script")||e.endsWith(".ns")}var scriptEditorRamCheck=null,scriptEditorRamText=null;function scriptEditorInit(){const e=document.getElementById("script-editor-buttons-wrapper");if(null==e)return console.error("Could not find 'script-editor-buttons-wrapper'"),!1;const t=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("button",{class:"std-button",display:"inline-block",innerText:"Beautify",clickListener:()=>{let e=getCurrentEditor();return null!=e&&e.beautifyScript(),!1}});scriptEditorRamText=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("p",{display:"inline-block",margin:"10px",id:"script-editor-status-text"});const n=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("label",{for:"script-editor-ram-check",margin:"4px",marginTop:"8px",innerText:"Dynamic RAM Usage Checker",color:"white",tooltip:"Enable/Disable the dynamic RAM Usage display. You may want to disable it for very long scripts because there may be performance issues"});(scriptEditorRamCheck=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("input",{type:"checkbox",name:"script-editor-ram-check",id:"script-editor-ram-check",margin:"4px",marginTop:"8px"})).checked=!0;const i=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("a",{class:"std-button",display:"inline-block",href:"https://bitburner.readthedocs.io/en/latest/index.html",innerText:"Netscript Documentation",target:"_blank"}),a=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_22__.createElement)("button",{class:"std-button",display:"inline-block",innerText:"Save & Close (Ctrl/Cmd + b)",clickListener:()=>(saveAndCloseScriptEditor(),!1)});e.appendChild(t),e.appendChild(a),e.appendChild(scriptEditorRamText),e.appendChild(scriptEditorRamCheck),e.appendChild(n),e.appendChild(i);const r={saveAndCloseFn:saveAndCloseScriptEditor,quitFn:_engine__WEBPACK_IMPORTED_MODULE_1__.Engine.loadTerminalContent};_ScriptEditor_Ace__WEBPACK_IMPORTED_MODULE_8__.a.init(r),_ScriptEditor_CodeMirror__WEBPACK_IMPORTED_MODULE_9__.a.init(r);const o=document.getElementById("script-editor-option-editor");if(null==o)return console.error("Could not find DOM Element for editor selector (id=script-editor-option-editor)"),!1;for(let e=0;e{const e=o.value;switch(e){case _Settings_SettingEnums__WEBPACK_IMPORTED_MODULE_12__.EditorSetting.Ace:const t=_ScriptEditor_CodeMirror__WEBPACK_IMPORTED_MODULE_9__.a.getCode(),n=_ScriptEditor_CodeMirror__WEBPACK_IMPORTED_MODULE_9__.a.getFilename();_ScriptEditor_Ace__WEBPACK_IMPORTED_MODULE_8__.a.create(),_ScriptEditor_CodeMirror__WEBPACK_IMPORTED_MODULE_9__.a.setInvisible(),_ScriptEditor_Ace__WEBPACK_IMPORTED_MODULE_8__.a.openScript(n,t);break;case _Settings_SettingEnums__WEBPACK_IMPORTED_MODULE_12__.EditorSetting.CodeMirror:const i=_ScriptEditor_Ace__WEBPACK_IMPORTED_MODULE_8__.a.getCode(),a=_ScriptEditor_Ace__WEBPACK_IMPORTED_MODULE_8__.a.getFilename();_ScriptEditor_CodeMirror__WEBPACK_IMPORTED_MODULE_9__.a.create(),_ScriptEditor_Ace__WEBPACK_IMPORTED_MODULE_8__.a.setInvisible(),_ScriptEditor_CodeMirror__WEBPACK_IMPORTED_MODULE_9__.a.openScript(a,i);break;default:return void console.error(`Unrecognized Editor Setting: ${e}`)}_Settings_Settings__WEBPACK_IMPORTED_MODULE_11__.Settings.Editor=e}),o.onchange()}function getCurrentEditor(){switch(_Settings_Settings__WEBPACK_IMPORTED_MODULE_11__.Settings.Editor){case _Settings_SettingEnums__WEBPACK_IMPORTED_MODULE_12__.EditorSetting.Ace:return _ScriptEditor_Ace__WEBPACK_IMPORTED_MODULE_8__.a;case _Settings_SettingEnums__WEBPACK_IMPORTED_MODULE_12__.EditorSetting.CodeMirror:return _ScriptEditor_CodeMirror__WEBPACK_IMPORTED_MODULE_9__.a;default:throw console.log(`Invalid Editor Setting: ${_Settings_Settings__WEBPACK_IMPORTED_MODULE_11__.Settings.Editor}`),new Error(`Invalid Editor Setting: ${_Settings_Settings__WEBPACK_IMPORTED_MODULE_11__.Settings.Editor}`)}}async function updateScriptEditorContent(){var e=document.getElementById("script-editor-filename").value;if(null==scriptEditorRamCheck||!scriptEditorRamCheck.checked||!isScriptFilename(e))return void(scriptEditorRamText.innerText="N/A");let t;try{t=getCurrentEditor().getCode()}catch(e){return void(scriptEditorRamText.innerText="RAM: ERROR")}var n=t.repeat(1),i=await calculateRamUsage(n);scriptEditorRamText.innerText=-1!==i?"RAM: "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_17__.numeralWrapper.format(i,"0.00")+" GB":"RAM: Syntax Error"}function saveAndCloseScriptEditor(){var e=document.getElementById("script-editor-filename").value;let t;try{t=getCurrentEditor().getCode()}catch(e){return void Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)("Something went wrong when trying to save (getCurrentEditor().getCode()). Please report to game developer with details")}if(_InteractiveTutorial__WEBPACK_IMPORTED_MODULE_3__.a.isRunning&&_InteractiveTutorial__WEBPACK_IMPORTED_MODULE_3__.a.currStep===_InteractiveTutorial__WEBPACK_IMPORTED_MODULE_3__.d.TerminalTypeScript){if("foodnstuff.script"!==e)return void Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)("Leave the script name as 'foodnstuff'!");if(-1==(t=t.replace(/\s/g,"")).indexOf("while(true){hack('foodnstuff');}"))return void Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_19__.dialogBoxCreate)("Please copy and paste the code from the tutorial!");let i=_Player__WEBPACK_IMPORTED_MODULE_7__.a.getCurrentServer();for(var n=0;n0;){const nextModule=parseQueue.shift();let code;if(nextModule.startsWith("https://")||nextModule.startsWith("http://"))try{const module=await eval("import(nextModule)");code="";for(const e in module)"function"==typeof module[e]&&(code+=module[e].toString()+";\n")}catch(e){return console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`),-1}else{const e=server.getScript(nextModule.startsWith("./")?nextModule.slice(2):nextModule);if(!e)return console.warn("Invalid script"),-1;code=e.code}parseCode(code,nextModule)}let ram=_Constants__WEBPACK_IMPORTED_MODULE_0__.CONSTANTS.ScriptBaseRamCost;const unresolvedRefs=Object.keys(dependencyMap).filter(e=>e.startsWith(initialModule)),resolvedRefs=new Set;for(;unresolvedRefs.length>0;){const e=unresolvedRefs.shift();if("hacknet"!==e||resolvedRefs.has("hacknet")||(ram+=_Constants__WEBPACK_IMPORTED_MODULE_0__.CONSTANTS.ScriptHacknetNodesRamCost),"document"!==e||resolvedRefs.has("document")||(ram+=_Constants__WEBPACK_IMPORTED_MODULE_0__.CONSTANTS.ScriptDomRamCost),"window"!==e||resolvedRefs.has("window")||(ram+=_Constants__WEBPACK_IMPORTED_MODULE_0__.CONSTANTS.ScriptDomRamCost),resolvedRefs.add(e),e.endsWith(".*")){const t=e.slice(0,e.length-2);for(let e of Object.keys(dependencyMap).filter(e=>e.startsWith(t)))for(let t of dependencyMap[e]||[])resolvedRefs.has(t)||unresolvedRefs.push(t)}else for(let t of dependencyMap[e]||[])resolvedRefs.has(t)||unresolvedRefs.push(t);try{function applyFuncRam(e){if("function"!=typeof e)return 0;try{let t;return"number"==typeof(t="AsyncFunction"===e.constructor.name?0:e.apply(null,[]))?t:0}catch(e){return console.log("ERROR applying function: "+e),0}}var func;func=e in workerScript.env.vars.bladeburner?workerScript.env.vars.bladeburner[e]:e in workerScript.env.vars.codingcontract?workerScript.env.vars.codingcontract[e]:e in workerScript.env.vars.gang?workerScript.env.vars.gang[e]:workerScript.env.get(e),ram+=applyFuncRam(func)}catch(e){continue}}return ram}catch(e){return-1}}function parseOnlyCalculateDeps(e,t){const n=Object(_utils_acorn__WEBPACK_IMPORTED_MODULE_15__.parse)(e,{sourceType:"module",ecmaVersion:8}),i=t+memCheckGlobalKey,a={};a[i]=new Set;let r={};var o=[];function s(e,n){const i=a[e]||(a[e]=new Set);n in r&&i.add(r[n]),i.add(t+"."+n),i.add(n)}const l=Object.getOwnPropertyNames(Object.prototype);function c(){return{Identifier:(e,t,n)=>{l.includes(e.name)||s(t.key,e.name)},WhileStatement:(e,t,n)=>{s(t.key,specialReferenceWHILE),e.test&&n(e.test,t),e.body&&n(e.body,t)},DoWhileStatement:(e,t,n)=>{s(t.key,specialReferenceWHILE),e.test&&n(e.test,t),e.body&&n(e.body,t)},ForStatement:(e,t,n)=>{s(t.key,specialReferenceFOR),e.init&&n(e.init,t),e.test&&n(e.test,t),e.update&&n(e.update,t),e.body&&n(e.body,t)},IfStatement:(e,t,n)=>{s(t.key,specialReferenceIF),e.test&&n(e.test,t),e.consequent&&n(e.consequent,t),e.alternate&&n(e.alternate,t)},MemberExpression:(e,t,n)=>{e.object&&n(e.object,t),e.property&&n(e.property,t)}}}return walk.recursive(n,{key:i},Object.assign({ImportDeclaration:(e,t,n)=>{const i=e.source.value;o.push(i),a[t.key].add(i+memCheckGlobalKey);for(let n=0;n{const a=t+"."+e.id.name;walk.recursive(e,{key:a},c())}},c())),{dependencyMap:a,additionalModules:o}}async function calculateRamUsage(e){var t=_Player__WEBPACK_IMPORTED_MODULE_7__.a.getCurrentServer(),n=new _NetscriptWorker__WEBPACK_IMPORTED_MODULE_6__.b({filename:"foo",scriptRef:{code:""},args:[],getCode:function(){return""}});n.checkingRam=!0,n.serverIp=t.ip;try{return await parseOnlyRamCalculate(t,e,n)}catch(e){console.log("Failed to parse ram using new method. Falling back.",e)}try{var i=Object(_utils_acorn__WEBPACK_IMPORTED_MODULE_15__.parse)(e,{sourceType:"module"})}catch(e){return-1}var a=[],r=_Constants__WEBPACK_IMPORTED_MODULE_0__.CONSTANTS.ScriptBaseRamCost,o=!1,s=!1,l=!1;for(a.push(i);0!=a.length;){var c=a.shift();switch(c.type){case"ImportDeclaration":for(var u=Object(_NetscriptEvaluator__WEBPACK_IMPORTED_MODULE_4__.a)(c,n,!0),p=0;p=1&&(n=1),e.dataMap)if(e.dataMap.hasOwnProperty(i)){if(0==e.dataMap[i][2]||null==e.dataMap[i][2])continue;if(null==(u=_Server__WEBPACK_IMPORTED_MODULE_10__.b[i]))continue;var a=Math.round(.5*e.dataMap[i][2]/e.onlineRunningTime*t);console.log(e.filename+" called grow() on "+u.hostname+" "+a+" times while offline"),e.log("Called grow() on "+u.hostname+" "+a+" times while offline");var r=Object(_Server__WEBPACK_IMPORTED_MODULE_10__.k)(u,450*a);e.log(u.hostname+" grown by "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_17__.numeralWrapper.format(100*r-100,"0.000000%")+" from grow() calls made while offline")}var o=0;for(var i in e.dataMap)if(e.dataMap.hasOwnProperty(i)){if(0==e.dataMap[i][0]||null==e.dataMap[i][0])continue;if(null==(u=_Server__WEBPACK_IMPORTED_MODULE_10__.b[i]))continue;var s=.5*e.dataMap[i][0]/e.onlineRunningTime*t;(s*=n)>u.moneyAvailable&&(s=u.moneyAvailable),o+=s,_Player__WEBPACK_IMPORTED_MODULE_7__.a.gainMoney(s),_Player__WEBPACK_IMPORTED_MODULE_7__.a.recordMoneySource(s,"hacking"),console.log(e.filename+" generated $"+s+" while offline by hacking "+u.hostname),e.log(e.filename+" generated $"+s+" while offline by hacking "+u.hostname),u.moneyAvailable-=s,u.moneyAvailable<0&&(u.moneyAvailable=0),isNaN(u.moneyAvailable)&&(u.moneyAvailable=0)}var l=e.onlineExpGained/e.onlineRunningTime*.5*t;for(var i in l*=n,_Player__WEBPACK_IMPORTED_MODULE_7__.a.gainHackingExp(l),e.offlineMoneyMade+=o,e.offlineRunningTime+=t,e.offlineExpGained+=l,e.dataMap)if(e.dataMap.hasOwnProperty(i)){if(0==e.dataMap[i][1]||null==e.dataMap[i][1])continue;if(null==(u=_Server__WEBPACK_IMPORTED_MODULE_10__.b[i]))continue;var c=Math.round(.5*e.dataMap[i][1]/e.onlineRunningTime*t);console.log(e.filename+" hacked "+u.hostname+" "+c+" times while offline"),e.log("Hacked "+u.hostname+" "+c+" times while offline"),u.fortify(_Constants__WEBPACK_IMPORTED_MODULE_0__.CONSTANTS.ServerFortifyAmount*c)}for(var i in e.dataMap)if(e.dataMap.hasOwnProperty(i)){if(0==e.dataMap[i][3]||null==e.dataMap[i][3])continue;var u;if(null==(u=_Server__WEBPACK_IMPORTED_MODULE_10__.b[i]))continue;var p=Math.round(.5*e.dataMap[i][3]/e.onlineRunningTime*t);console.log(e.filename+" called weaken() on "+u.hostname+" "+p+" times while offline"),e.log("Called weaken() on "+u.hostname+" "+p+" times while offline"),u.weaken(_Constants__WEBPACK_IMPORTED_MODULE_0__.CONSTANTS.ServerWeakenAmount*p)}return o}function findRunningScript(e,t,n){for(var i=0;i0)return this.ramUsage;const e=_Server__WEBPACK_IMPORTED_MODULE_10__.b[this.server];if(null==e)return 0;for(let t=0;t_Settings_Settings__WEBPACK_IMPORTED_MODULE_11__.Settings.MaxLogCapacity&&this.logs.shift();let t=e;_Fconf__WEBPACK_IMPORTED_MODULE_2__.a.ENABLE_TIMESTAMPS&&(t="["+Object(_utils_helpers_getTimestamp__WEBPACK_IMPORTED_MODULE_23__.getTimestamp)()+"] "+t),this.logs.push(t),this.logUpd=!0},RunningScript.prototype.displayLog=function(){for(var e=0;e{r=e,null!=t.CompanyPositions[r.name]&&console.warn(`Duplicate Company Position being defined: ${r.name}`),t.CompanyPositions[r.name]=new a.CompanyPosition(r)})},function(e,t,n){"use strict";(function(e){n.d(t,"a",function(){return J});var i=n(13),a=n(6),r=n(20),o=n(1),s=n(10),l=n(63),c=n(15),u=n(51),p=n(5),m=n(0),d=n(68),h=n(3),g=n(56),_=n(29),y=n(93),f=n(9),b=n(27),E=n(14),v=n(77),k=n(60),C=n(91),O=n(2),T=n(37),S=n(12),M=n(40),P=n(4),A=n(16),w=n(105),x=n(67),R=n(23);const N=["Aevum","Chongqing","Sector-12","New Tokyo","Ishima","Volhaven"],I=1e9;var L="bladeburner-active-action",D=0,B={helpList:"Use 'help [command]' to get more information about a particular Bladeburner console command.

automate [var] [val] [hi/low] Configure simple automation for Bladeburner tasks
clear/cls Clear the console
help [cmd] Display this help text, or help text for a specific command
log [en/dis] [type] Enable or disable logging for events and actions
skill [action] [name] Level or display info about your Bladeburner skills
start [type] [name] Start a Bladeburner action/task
stop Stops your current Bladeburner action/task
",automate:'automate [var] [val] [hi/low]

A simple way to automate your Bladeburner actions. This console command can be used to automatically start an action when your stamina rises above a certain threshold, and automatically switch to another action when your stamina drops below another threshold.

automate status - Check the current status of your automation and get a brief description of what it\'ll do
automate en - Enable the automation feature
automate dis - Disable the automation feature

There are four properties that must be set for this automation to work properly. Here is how to set them:

automate stamina 100 high
automate contract Tracking high
automate stamina 50 low
automate general "Field Analysis" low

Using the four console commands above will set the automation to perform Tracking contracts if your stamina is 100 or higher, and then switch to Field Analysis if your stamina drops below 50. Note that when setting the action, the name of the action is CASE-SENSITIVE. It must exactly match whatever the name is in the UI.',clear:"clear

Clears the console",cls:"cls

Clears the console",help:"help [command]

Running 'help' with no arguments displays the general help text, which lists all console commands and a brief description of what they do. A command can be specified to get more specific help text about that particular command. For example:

help automate

will display specific information about using the automate console command",log:"log [en/dis] [type]

Enable or disable logging. By default, the results of completing actions such as contracts/operations are logged in the console. There are also random events that are logged in the console as well. The five categories of things that get logged are:

[general, contracts, ops, blackops, events]

The logging for these categories can be enabled or disabled like so:

log dis contracts - Disables logging that occurs when contracts are completed
log en contracts - Enables logging that occurs when contracts are completed
log dis events - Disables logging for Bladeburner random events

Logging can be universally enabled/disabled using the 'all' keyword:

log dis all
log en all",skill:'skill [action] [name]

Level or display information about your skills.

To display information about all of your skills and your multipliers, use:

skill list

To display information about a specific skill, specify the name of the skill afterwards. Note that the name of the skill is case-sensitive. Enter it exactly as seen in the UI. If the name of the skill has whitespace, enclose the name of the skill in double quotation marks:

skill list Reaper
skill list "Digital Observer"

This console command can also be used to level up skills:

skill level [skill name]',start:'start [type] [name]

Start an action. An action is specified by its type and its name. The name is case-sensitive. It must appear exactly as it does in the UI. If the name of the action has whitespace, enclose it in double quotation marks. Valid action types include:

[general, contract, op, blackop]

Examples:

start contract Tracking
start op "Undercover Operation"
',stop:"stop

Stop your current action and go idle"};function j(e={}){this.name=e.name?e.name:p.Locations.Sector12,this.pop=e.pop?e.pop:Object(A.getRandomInt)(I,1.5*I),this.popEst=this.pop*(Math.random()+.5),this.comms=e.comms?e.comms:Object(A.getRandomInt)(5,150),this.commsEst=this.comms+Object(A.getRandomInt)(-5,5),this.commsEst<0&&(this.commsEst=0),this.chaos=0}function W(e={name:"foo",desc:"foo"}){if(!e.name)throw new Error("Failed to initialize Bladeburner Skill. No name was specified in ctor");if(this.name=e.name,!e.desc)throw new Error("Failed to initialize Bladeburner Skills. No desc was specified in ctor");this.desc=e.desc,this.baseCost=e.baseCost?e.baseCost:1,this.costInc=e.costInc?e.costInc:1,e.maxLvl&&(this.maxLvl=e.maxLvl),e.successChanceAll&&(this.successChanceAll=e.successChanceAll),e.successChanceStealth&&(this.successChanceStealth=e.successChanceStealth),e.successChanceKill&&(this.successChanceKill=e.successChanceKill),e.successChanceContract&&(this.successChanceContract=e.successChanceContract),e.successChanceOperation&&(this.successChanceOperation=e.successChanceOperation),e.successChanceEstimate&&(this.successChanceEstimate=e.successChanceEstimate),e.actionTime&&(this.actionTime=e.actionTime),e.effHack&&(this.effHack=e.effHack),e.effStr&&(this.effStr=e.effStr),e.effDef&&(this.effDef=e.effDef),e.effDex&&(this.effDex=e.effDex),e.effAgi&&(this.effAgi=e.effAgi),e.effCha&&(this.effCha=e.effCha),e.stamina&&(this.stamina=e.stamina),e.money&&(this.money=e.money),e.expGain&&(this.expGain=e.expGain),e.weaponAbility&&(this.weaponAbility=e.weaponAbility),e.gunAbility&&(this.gunAbility=e.gunAbility)}e(document).keydown(function(e){if(S.routing.isOn(S.Page.Bladeburner)){if(!(m.a.bladeburner instanceof J))return;let r=m.a.bladeburner.consoleHistory;if(e.keyCode===_.KEY.ENTER){e.preventDefault();var t=X.consoleInput.value;t.length>0&&(m.a.bladeburner.postToConsole("> "+t),m.a.bladeburner.resetConsoleInput(),m.a.bladeburner.executeConsoleCommands(t))}if(e.keyCode===_.KEY.UPARROW){if(null==X.consoleInput)return;var n=D;if(0===(a=r.length))return;(n<0||n>a)&&(D=a),0!==n&&--D;var i=r[D];X.consoleInput.value=i,Object(g.setTimeoutRef)(function(){X.consoleInput.selectionStart=X.consoleInput.selectionEnd=1e4},0)}if(e.keyCode===_.KEY.DOWNARROW){if(null==X.consoleInput)return;var a;n=D;if(0==(a=r.length))return;if((n<0||n>a)&&(D=a),n==a||n==a-1)D=a,X.consoleInput.value="";else{i=r[++D];X.consoleInput.value=i}}}}),j.prototype.improvePopulationEstimateByCount=function(e){if(isNaN(e))throw new Error("NaN passeed into City.improvePopulationEstimateByCount()");this.popEstthis.pop&&(this.popEst=this.pop)):this.popEst>this.pop&&(this.popEst-=e,this.popEstthis.pop&&(this.popEst=this.pop)):this.popEst>this.pop&&(this.popEst*=1-e/100,this.popEstthis.comms&&(this.commsEst=this.comms)):this.commsEst>this.comms&&(this.commsEst-=e,this.commsEst0?1:-1),this.pop+=n,t.changeEstEqually&&(this.popEst+=n,this.popEst<0&&(this.popEst=0)),n}},j.prototype.changeChaosByCount=function(e){if(isNaN(e))throw new Error("NaN passed into City.changeChaosByCount()");0!==e&&(this.chaos+=e,this.chaos<0&&(this.chaos=0))},j.prototype.changeChaosByPercentage=function(e){if(isNaN(e))throw new Error("NaN passed into City.chaosChaosByPercentage()");if(0!==e){var t=this.chaos*(e/100);this.chaos+=t,this.chaos<0&&(this.chaos=0)}},j.prototype.toJSON=function(){return Object(E.Generic_toJSON)("City",this)},j.fromJSON=function(e){return Object(E.Generic_fromJSON)(j,e.data)},E.Reviver.constructors.City=j,W.prototype.calculateCost=function(e){return Math.floor((this.baseCost+e*this.costInc)*r.BitNodeMultipliers.BladeburnerSkillCost)};var F={},U={BladesIntuition:"Blade's Intuition",Cloak:"Cloak",Marksman:"Marksman",WeaponProficiency:"Weapon Proficiency",ShortCircuit:"Short-Circuit",DigitalObserver:"Digital Observer",Tracer:"Tracer",Overclock:"Overclock",Reaper:"Reaper",EvasiveSystem:"Evasive System",Datamancer:"Datamancer",CybersEdge:"Cyber's Edge",HandsOfMidas:"Hands of Midas",Hyperdrive:"Hyperdrive"};function H(e={}){this.name=e.name?e.name:"",this.desc=e.desc?e.desc:"",this.level=1,this.maxLevel=1,this.autoLevel=!0,this.baseDifficulty=e.baseDifficulty?Object(v.addOffset)(e.baseDifficulty,10):100,this.difficultyFac=e.difficultyFac?e.difficultyFac:1.01,this.rewardFac=e.rewardFac?e.rewardFac:1.02,this.successes=0,this.failures=0,this.rankGain=e.rankGain?e.rankGain:0,e.rankLoss&&(this.rankLoss=e.rankLoss),e.hpLoss&&(this.hpLoss=e.hpLoss,this.hpLost=0),this.isStealth=!!e.isStealth,this.isKill=!!e.isKill,this.count=e.count?e.count:Object(A.getRandomInt)(1e3,25e3),this.countGrowth=e.countGrowth?e.countGrowth:Object(A.getRandomInt)(1,5);this.weights=e.weights?e.weights:{hack:1/7,str:1/7,def:1/7,dex:1/7,agi:1/7,cha:1/7,int:1/7};var t=0;for(var n in this.weights)this.weights.hasOwnProperty(n)&&(t+=this.weights[n]);if(t-1>=10*Number.EPSILON)throw new Error("Invalid weights when constructing Action "+this.name+". The weights should sum up to 1. They sum up to :1");for(var i in this.decays=e.decays?e.decays:{hack:.9,str:.9,def:.9,dex:.9,agi:.9,cha:.9,int:.9},this.decays)if(this.decays.hasOwnProperty(i)&&this.decays[i]>1)throw new Error("Invalid decays when constructing Action "+this.name+". Decay value cannot be greater than 1")}H.prototype.getDifficulty=function(){var e=this.baseDifficulty*Math.pow(this.difficultyFac,this.level-1);if(isNaN(e))throw new Error("Calculated NaN in Action.getDifficulty()");return e},H.prototype.getSuccessChance=function(e,t={}){if(null==e)throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance");var n=this.getDifficulty(),i=0;for(var a in this.weights)if(this.weights.hasOwnProperty(a)){var r=m.a.queryStatFromString(a),o="eff"+a.charAt(0).toUpperCase()+a.slice(1),s=e.skillMultipliers[o];null==s&&(console.log("ERROR: Failed to find Bladeburner Skill multiplier for: "+a),s=1),i+=this.weights[a]*Math.pow(s*r,this.decays[a])}(i*=e.calculateStaminaPenalty(),this instanceof Y||this instanceof z)&&(this.teamCount&&this.teamCount>0&&(this.teamCount=Math.min(this.teamCount,e.teamSize),i*=Math.pow(this.teamCount,.05)));if(!(this instanceof z)){var l=e.getCurrentCity();if(t.est?i*=Math.pow(l.popEst/I,.7):i*=Math.pow(l.pop/I,.7),l.chaos>50){var c=l.chaos-50+1;n*=Math.pow(c,.1)}if(this instanceof Y&&"Raid"===this.name&&l.comms<=0)return 0}if(i*=e.skillMultipliers.successChanceAll,(this instanceof Y||this instanceof z)&&(i*=e.skillMultipliers.successChanceOperation),this instanceof $&&(i*=e.skillMultipliers.successChanceContract),this.isStealth&&(i*=e.skillMultipliers.successChanceStealth),this.isKill&&(i*=e.skillMultipliers.successChanceKill),i*=m.a.bladeburner_success_chance_mult,isNaN(i))throw new Error("Competence calculated as NaN in Action.getSuccessChance()");return Math.min(1,i/n)},H.prototype.attempt=function(e){return Math.random()=this.getSuccessesNeededForNextLevel(e)&&++this.maxLevel},H.prototype.toJSON=function(){return Object(E.Generic_toJSON)("Action",this)},H.fromJSON=function(e){return Object(E.Generic_fromJSON)(H,e.data)},E.Reviver.constructors.Action=H;var G={};const K=Object.freeze({Idle:1,Contract:2,Operation:3,BlackOp:4,BlackOperation:4,Training:5,Recruitment:6,FieldAnalysis:7,"Field Analysis":7,Diplomacy:8,"Hyperbolic Regeneration Chamber":9});function q(e={}){e.name&&(this.name=e.name),e.type&&(this.type=e.type)}function $(e={}){H.call(this,e)}function Y(e={}){H.call(this,e),this.reqdRank=e.reqdRank?e.reqdRank:100,this.teamCount=e.teamCount?e.teamCount:0}function z(e={}){Y.call(this,e),this.count=1,this.countGrowth=0}q.prototype.toJSON=function(){return Object(E.Generic_toJSON)("ActionIdentifier",this)},q.fromJSON=function(e){return Object(E.Generic_fromJSON)(q,e.data)},E.Reviver.constructors.ActionIdentifier=q,$.prototype=Object.create(H.prototype),$.prototype.toJSON=function(){return Object(E.Generic_toJSON)("Contract",this)},$.fromJSON=function(e){return Object(E.Generic_fromJSON)($,e.data)},E.Reviver.constructors.Contract=$,Y.prototype=Object.create(H.prototype),Y.prototype.toJSON=function(){return Object(E.Generic_toJSON)("Operation",this)},Y.fromJSON=function(e){return Object(E.Generic_fromJSON)(Y,e.data)},E.Reviver.constructors.Operation=Y,z.prototype=Object.create(H.prototype),z.prototype.toJSON=function(){return Object(E.Generic_toJSON)("BlackOperation",this)},z.fromJSON=function(e){return Object(E.Generic_fromJSON)(z,e.data)},E.Reviver.constructors.BlackOperation=z;var V={};function J(e={}){this.numHosp=0,this.moneyLost=0,this.rank=0,this.maxRank=0,this.skillPoints=0,this.totalSkillPoints=0,this.teamSize=0,this.teamLost=0,this.storedCycles=0,this.randomEventCounter=Object(A.getRandomInt)(240,600),this.actionTimeToComplete=0,this.actionTimeCurrent=0;var t=K.Idle;this.action=new q({type:t}),this.cities={};for(var n=0;n
Does NOT require stamina."}),G[e="Recruitment"]=new H({name:e,desc:"Attempt to recruit members for your Bladeburner team. These members can help you conduct operations.

Does NOT require stamina."}),G[e="Diplomacy"]=new H({name:e,desc:"Improve diplomatic relations with the Synthoid population. Completing this action will reduce the Chaos level in your current city.

Does NOT require stamina."}),G[e="Hyperbolic Regeneration Chamber"]=new H({name:e,desc:"Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. This will slowly heal your wounds and slightly increase your stamina gain.

"}),V["Operation Typhoon"]=new z({name:"Operation Typhoon",desc:"Obadiah Zenyatta is the leader of a RedWater PMC. It has long been known among the intelligence community that Zenyatta, along with the rest of the PMC, is a Synthoid.

The goal of Operation Typhoon is to find and eliminate Zenyatta and RedWater by any means necessary. After the task is completed, the actions must be covered up from the general public.",baseDifficulty:2e3,reqdRank:2500,rankGain:50,rankLoss:10,hpLoss:100,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Zero"]=new z({name:"Operation Zero",desc:"AeroCorp is one of the world's largest defense contractors. It's leader, Steve Watataki, is thought to be a supporter of Synthoid rights. He must be removed.

The goal of Operation Zero is to covertly infiltrate AeroCorp and uncover any incriminating evidence or information against Watataki that will cause him to be removed from his position at AeroCorp. Incriminating evidence can be fabricated as a last resort. Be warned that AeroCorp has some of the most advanced security measures in the world.",baseDifficulty:2500,reqdRank:5e3,rankGain:60,rankLoss:15,hpLoss:50,weights:{hack:.2,str:.15,def:.15,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isStealth:!0}),V["Operation X"]=new z({name:"Operation X",desc:"We have recently discovered an underground publication group called Samizdat. Even though most of their publications are nonsensical conspiracy theories, the average human is gullible enough to believe them. Many of their works discuss Synthoids and pose a threat to society. The publications are spreading rapidly in China and other Eastern countries.

Samizdat has done a good job of keeping hidden and anonymous. However, we've just received intelligence that their base of operations is in Ishima's underground sewer systems. Your task is to investigate the sewer systems, and eliminate Samizdat. They must never publish anything again.",baseDifficulty:3e3,reqdRank:7500,rankGain:75,rankLoss:15,hpLoss:100,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Titan"]=new z({name:"Operation Titan",desc:"Several months ago Titan Laboratories' Bioengineering department was infiltrated by Synthoids. As far as we know, Titan Laboratories' management has no knowledge about this. We don't know what the Synthoids are up to, but the research that they could be conducting using Titan Laboraties' vast resources is potentially very dangerous.

Your goal is to enter and destroy the Bioengineering department's facility in Aevum. The task is not just to retire the Synthoids there, but also to destroy any information or research at the facility that is relevant to the Synthoids and their goals.",baseDifficulty:4e3,reqdRank:1e4,rankGain:100,rankLoss:20,hpLoss:100,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Ares"]=new z({name:"Operation Ares",desc:"One of our undercover agents, Agent Carter, has informed us of a massive weapons deal going down in Dubai between rogue Russian militants and a radical Synthoid community. These weapons are next-gen plasma and energy weapons. It is critical for the safety of humanity that this deal does not happen.

Your task is to intercept the deal. Leave no survivors.",baseDifficulty:5e3,reqdRank:12500,rankGain:125,rankLoss:20,hpLoss:200,weights:{hack:0,str:.25,def:.25,dex:.25,agi:.25,cha:0,int:0},decays:{hack:0,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Archangel"]=new z({name:"Operation Archangel",desc:"Our analysts have discovered that the popular Red Rabbit brothel in Amsterdam is run and 'staffed' by MK-VI Synthoids. Intelligence suggests that the profit from this brothel is used to fund a large black market arms trafficking operation.

The goal of this operation is to take out the leaders that are running the Red Rabbit brothel. Try to limit the number of other casualties, but do what you must to complete the mission.",baseDifficulty:7500,reqdRank:15e3,rankGain:200,rankLoss:20,hpLoss:25,weights:{hack:0,str:.2,def:.2,dex:.3,agi:.3,cha:0,int:0},decays:{hack:0,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Juggernaut"]=new z({name:"Operation Juggernaut",desc:"The CIA has just encountered a new security threat. A new criminal group, lead by a shadowy operative who calls himself Juggernaut, has been smuggling drugs and weapons (including suspected bioweapons) into Sector-12. We also have reason to believe the tried to break into one of Universal Energy's facilities in order to cause a city-wide blackout. The CIA suspects that Juggernaut is a heavily-augmented Synthoid, and have thus enlisted our help.

Your mission is to eradicate Juggernaut and his followers.",baseDifficulty:1e4,reqdRank:2e4,rankGain:300,rankLoss:40,hpLoss:300,weights:{hack:0,str:.25,def:.25,dex:.25,agi:.25,cha:0,int:0},decays:{hack:0,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Red Dragon"]=new z({name:"Operation Red Dragon",desc:"The Tetrads criminal organization is suspected of reverse-engineering the MK-VI Synthoid design. We believe they altered and possibly improved the design and began manufacturing their own Synthoid models in order to bolster their criminal activities.

Your task is to infiltrate and destroy the Tetrads' base of operations in Los Angeles. Intelligence tells us that their base houses one of their Synthoid manufacturing units.",baseDifficulty:12500,reqdRank:25e3,rankGain:500,rankLoss:50,hpLoss:500,weights:{hack:.05,str:.2,def:.2,dex:.25,agi:.25,cha:0,int:.05},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation K"]=new z({name:"Operation K",desc:"CODE RED SITUATION. Our intelligence tells us that VitaLife has discovered a new android cloning technology. This technology is supposedly capable of cloning Synthoid, not only physically but also their advanced AI modules. We do not believe that VitaLife is trying to use this technology illegally or maliciously, but if any Synthoids were able to infiltrate the corporation and take advantage of this technology then the results would be catastrophic.

We do not have the power or jurisdiction to shutdown this down through legal or political means, so we must resort to a covert operation. Your goal is to destroy this technology and eliminateanyone who was involved in its creation.",baseDifficulty:15e3,reqdRank:3e4,rankGain:750,rankLoss:60,hpLoss:1e3,weights:{hack:.05,str:.2,def:.2,dex:.25,agi:.25,cha:0,int:.05},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Deckard"]=new z({name:"Operation Deckard",desc:"Despite your success in eliminating VitaLife's new android-replicating technology in Operation K, we've discovered that a small group of MK-VI Synthoids were able to make off with the schematics and design of the technology before the Operation. It is almost a certainty that these Synthoids are some of the rogue MK-VI ones from the Synthoid Uprising.The goal of Operation Deckard is to hunt down these Synthoids and retire them. I don't need to tell you how critical this mission is.",baseDifficulty:2e4,reqdRank:4e4,rankGain:1e3,rankLoss:75,hpLoss:200,weights:{hack:0,str:.24,def:.24,dex:.24,agi:.24,cha:0,int:.04},decays:{hack:0,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Tyrell"]=new z({name:"Operation Tyrell",desc:"A week ago Blade Industries reported a small break-in at one of their Aevum Augmentation storage facitilities. We figured out that The Dark Army was behind the heist, and didn't think any more of it. However, we've just discovered that several known MK-VI Synthoids were part of that break-in group.

We cannot have Synthoids upgrading their already-enhanced abilities with Augmentations. Your task is to hunt down the associated Dark Army members and eliminate them.",baseDifficulty:25e3,reqdRank:5e4,rankGain:1500,rankLoss:100,hpLoss:500,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Wallace"]=new z({name:"Operation Wallace",desc:"Based on information gathered from Operation Tyrell, we've discovered that The Dark Army was well aware that there were Synthoids amongst their ranks. Even worse, we believe that The Dark Army is working together with other criminal organizations such as The Syndicate and that they are planning some sort of large-scale takeover of multiple major cities, most notably Aevum. We suspect that Synthoids have infiltrated the ranks of these criminal factions and are trying to stage another Synthoid uprising.

The best way to deal with this is to prevent it before it even happens. The goal of Operation Wallace is to destroy the Dark Army and Syndicate factions in Aevum immediately. Leave no survivors.",baseDifficulty:3e4,reqdRank:75e3,rankGain:2e3,rankLoss:150,hpLoss:1500,weights:{hack:0,str:.24,def:.24,dex:.24,agi:.24,cha:0,int:.04},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Shoulder of Orion"]=new z({name:"Operation Shoulder of Orion",desc:"China's Solaris Space Systems is secretly launching the first manned spacecraft in over a decade using Synthoids. We believe China is trying to establish the first off-world colonies.

The mission is to prevent this launch without instigating an international conflict. When you accept this mission you will be officially disavowed by the NSA and the national government until after you successfully return. In the event of failure, all of the operation's team members must not let themselves be captured alive.",baseDifficulty:35e3,reqdRank:1e5,rankGain:2500,rankLoss:500,hpLoss:1500,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isStealth:!0}),V["Operation Hyron"]=new z({name:"Operation Hyron",desc:"Our intelligence tells us that Fulcrum Technologies is developing a quantum supercomputer using human brains as core processors. This supercomputer is rumored to be able to store vast amounts of data and perform computations unmatched by any other supercomputer on the planet. But more importantly, the use of organic human brains means that the supercomputer may be able to reason abstractly and become self-aware.

I do not need to remind you why sentient-level AIs pose a serious thread to all of mankind.

The research for this project is being conducted at one of Fulcrum Technologies secret facilities in Aevum, codenamed 'Alpha Ranch'. Infiltrate the compound, delete and destroy the work, and then find and kill the project lead.",baseDifficulty:4e4,reqdRank:125e3,rankGain:3e3,rankLoss:1e3,hpLoss:500,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Morpheus"]=new z({name:"Operation Morpheus",desc:"DreamSense Technologies is an advertising company that uses special technology to transmit their ads into the peoples dreams and subconcious. They do this using broadcast transmitter towers. Based on information from our agents and informants in Chonqging, we have reason to believe that one of the broadcast towers there has been compromised by Synthoids and is being used to spread pro-Synthoid propaganda.

The mission is to destroy this broadcast tower. Speed and stealth are of the upmost important for this.",baseDifficulty:45e3,reqdRank:15e4,rankGain:4e3,rankLoss:1e3,hpLoss:100,weights:{hack:.05,str:.15,def:.15,dex:.3,agi:.3,cha:0,int:.05},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isStealth:!0}),V["Operation Ion Storm"]=new z({name:"Operation Ion Storm",desc:"Our analysts have uncovered a gathering of MK-VI Synthoids that have taken up residence in the Sector-12 Slums. We don't know if they are rogue Synthoids from the Uprising, but we do know that they have been stockpiling weapons, money, and other resources. This makes them dangerous.

This is a full-scale assault operation to find and retire all of these Synthoids in the Sector-12 Slums.",baseDifficulty:5e4,reqdRank:175e3,rankGain:5e3,rankLoss:1e3,hpLoss:5e3,weights:{hack:0,str:.24,def:.24,dex:.24,agi:.24,cha:0,int:.04},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Annihilus"]=new z({name:"Operation Annihilus",desc:"Our superiors have ordered us to eradicate everything and everyone in an underground facility located in Aevum. They tell us that the facility houses many dangerous Synthoids and belongs to a terrorist organization called 'The Covenant'. We have no prior intelligence about this organization, so you are going in blind.",baseDifficulty:55e3,reqdRank:2e5,rankGain:7500,rankLoss:1e3,hpLoss:1e4,weights:{hack:0,str:.24,def:.24,dex:.24,agi:.24,cha:0,int:.04},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Ultron"]=new z({name:"Operation Ultron",desc:"OmniTek Incorporated, the original designer and manufacturer of Synthoids, has notified us of a malfunction in their AI design. This malfunction, when triggered, causes MK-VI Synthoids to become radicalized and seek out the destruction of humanity. They say that this bug affects all MK-VI Synthoids, not just the rogue ones from the Uprising.

OmniTek has also told us they they believe someone has triggered this malfunction in a large group of MK-VI Synthoids, and that these newly-radicalized Synthoids are now amassing in Volhaven to form a terrorist group called Ultron.

Intelligence suggests Ultron is heavily armed and that their members are augmented. We believe Ultron is making moves to take control of and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).

Your task is to find and destroy Ultron.",baseDifficulty:6e4,reqdRank:25e4,rankGain:1e4,rankLoss:2e3,hpLoss:1e4,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),V["Operation Centurion"]=new z({name:"Operation Centurion",desc:"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

Throughout all of humanity's history, we have relied on technology to survive, conquer, and progress. Its advancement became our primary goal. And at the peak of human civilization technology turned into power. Global, absolute power.

It seems that the universe is not without a sense of irony.

D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",baseDifficulty:7e4,reqdRank:3e5,rankGain:15e3,rankLoss:5e3,hpLoss:1e4,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75}}),V["Operation Vindictus"]=new z({name:"Operation Vindictus",desc:"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

The bits are all around us. The daemons that hold the Node together can manifest themselves in many different ways.

D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",baseDifficulty:75e3,reqdRank:35e4,rankGain:2e4,rankLoss:2e4,hpLoss:2e4,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75}}),V["Operation Daedalus"]=new z({name:"Operation Daedalus",desc:"Yesterday we obeyed kings and bent our neck to emperors. Today we kneel only to truth.",baseDifficulty:8e4,reqdRank:4e5,rankGain:4e4,rankLoss:1e4,hpLoss:1e5,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75}})}(),this.initializeDomElementRefs(),e.new&&this.create()}J.prototype.prestige=function(){this.resetAction();var e=c.Factions.Bladeburners;this.rank>=25&&Object(u.c)(e)},J.prototype.create=function(){this.contracts.Tracking=new $({name:"Tracking",desc:"Identify and locate Synthoids. This contract involves reconnaissance and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.

Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for whatever city you are currently in.",baseDifficulty:125,difficultyFac:1.02,rewardFac:1.041,rankGain:.3,hpLoss:.5,count:Object(A.getRandomInt)(25,150),countGrowth:Object(A.getRandomInt)(5,75)/10,weights:{hack:0,str:.05,def:.05,dex:.35,agi:.35,cha:.1,int:.05},decays:{hack:0,str:.91,def:.91,dex:.91,agi:.91,cha:.9,int:1},isStealth:!0}),this.contracts["Bounty Hunter"]=new $({name:"Bounty Hunter",desc:"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.

Successfully completing a Bounty Hunter contract will lower the population in your current city, and will also increase its chaos level.",baseDifficulty:250,difficultyFac:1.04,rewardFac:1.085,rankGain:.9,hpLoss:1,count:Object(A.getRandomInt)(5,150),countGrowth:Object(A.getRandomInt)(5,75)/10,weights:{hack:0,str:.15,def:.15,dex:.25,agi:.25,cha:.1,int:.1},decays:{hack:0,str:.91,def:.91,dex:.91,agi:.91,cha:.8,int:.9},isKill:!0}),this.contracts.Retirement=new $({name:"Retirement",desc:"Hunt down and retire (kill) rogue Synthoids.

Successfully copmleting a Retirement contract will lower the population in your current city, and will also increase its chaos level.",baseDifficulty:200,difficultyFac:1.03,rewardFac:1.065,rankGain:.6,hpLoss:1,count:Object(A.getRandomInt)(5,150),countGrowth:Object(A.getRandomInt)(5,75)/10,weights:{hack:0,str:.2,def:.2,dex:.2,agi:.2,cha:.1,int:.1},decays:{hack:0,str:.91,def:.91,dex:.91,agi:.91,cha:.8,int:.9},isKill:!0}),this.operations.Investigation=new Y({name:"Investigation",desc:"As a field agent, investigate and identify Synthoid populations, movements, and operations.

Successful Investigation ops will increase the accuracy of your synthoid data.

You will NOT lose HP from failed Investigation ops.",baseDifficulty:400,difficultyFac:1.03,rewardFac:1.07,reqdRank:25,rankGain:2.2,rankLoss:.2,count:Object(A.getRandomInt)(1,100),countGrowth:Object(A.getRandomInt)(10,40)/10,weights:{hack:.25,str:.05,def:.05,dex:.2,agi:.1,cha:.25,int:.1},decays:{hack:.85,str:.9,def:.9,dex:.9,agi:.9,cha:.7,int:.9},isStealth:!0}),this.operations["Undercover Operation"]=new Y({name:"Undercover Operation",desc:"Conduct undercover operations to identify hidden and underground Synthoid communities and organizations.

Successful Undercover ops will increase the accuracy of your synthoid data.",baseDifficulty:500,difficultyFac:1.04,rewardFac:1.09,reqdRank:100,rankGain:4.4,rankLoss:.4,hpLoss:2,count:Object(A.getRandomInt)(1,100),countGrowth:Object(A.getRandomInt)(10,40)/10,weights:{hack:.2,str:.05,def:.05,dex:.2,agi:.2,cha:.2,int:.1},decays:{hack:.8,str:.9,def:.9,dex:.9,agi:.9,cha:.7,int:.9},isStealth:!0}),this.operations["Sting Operation"]=new Y({name:"Sting Operation",desc:"Conduct a sting operation to bait and capture particularly notorious Synthoid criminals.",baseDifficulty:650,difficultyFac:1.04,rewardFac:1.095,reqdRank:500,rankGain:5.5,rankLoss:.5,hpLoss:2.5,count:Object(A.getRandomInt)(1,150),countGrowth:Object(A.getRandomInt)(3,40)/10,weights:{hack:.25,str:.05,def:.05,dex:.25,agi:.1,cha:.2,int:.1},decays:{hack:.8,str:.85,def:.85,dex:.85,agi:.85,cha:.7,int:.9},isStealth:!0}),this.operations.Raid=new Y({name:"Raid",desc:"Lead an assault on a known Synthoid community. Note that there must be an existing Synthoid community in your current city in order for this Operation to be successful",baseDifficulty:800,difficultyFac:1.045,rewardFac:1.1,reqdRank:3e3,rankGain:55,rankLoss:2.5,hpLoss:50,count:Object(A.getRandomInt)(1,150),countGrowth:Object(A.getRandomInt)(2,40)/10,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.7,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.9},isKill:!0}),this.operations["Stealth Retirement Operation"]=new Y({name:"Stealth Retirement Operation",desc:"Lead a covert operation to retire Synthoids. The objective is to complete the task without drawing any attention. Stealth and discretion are key.",baseDifficulty:1e3,difficultyFac:1.05,rewardFac:1.11,reqdRank:2e4,rankGain:22,rankLoss:2,hpLoss:10,count:Object(A.getRandomInt)(1,150),countGrowth:Object(A.getRandomInt)(1,20)/10,weights:{hack:.1,str:.1,def:.1,dex:.3,agi:.3,cha:0,int:.1},decays:{hack:.7,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.9},isStealth:!0,isKill:!0}),this.operations.Assassination=new Y({name:"Assassination",desc:"Assassinate Synthoids that have been identified as important, high-profile social and political leaders in the Synthoid communities.",baseDifficulty:1500,difficultyFac:1.06,rewardFac:1.14,reqdRank:5e4,rankGain:44,rankLoss:4,hpLoss:5,count:Object(A.getRandomInt)(1,150),countGrowth:Object(A.getRandomInt)(1,20)/10,weights:{hack:.1,str:.1,def:.1,dex:.3,agi:.3,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.8},isStealth:!0,isKill:!0})},J.prototype.storeCycles=function(e=1){this.storedCycles+=e},J.prototype.process=function(){if(!1===d.b&&this.blackops.hasOwnProperty("Operation Daedalus"))return Object(d.a)(m.a.bitNodeN);if(!1===i.Augmentations[a.AugmentationNames.BladesSimulacrum].owned&&m.a.isWorking){if(this.action.type!==K.Idle){let e="Your Bladeburner action was cancelled because you started doing something else.";this.automateEnabled&&(e+="

Your automation was disabled as well. You will have to re-enable it through the Bladeburner console",this.automateEnabled=!1),Object(f.dialogBoxCreate)(e)}this.resetAction()}if(this.stamina<=0&&(this.log("Your Bladeburner action was cancelled because your stamina hit 0"),this.resetAction()),this.storedCycles>=5){var e=Math.floor(this.storedCycles/5);for(var t in e=Math.min(e,5),this.storedCycles-=5*e,this.calculateMaxStamina(),this.stamina+=this.calculateStaminaGainPerSecond()*e,this.stamina=Math.min(this.maxStamina,this.stamina),this.contracts)if(this.contracts.hasOwnProperty(t)){var n=this.contracts[t];n.count+=e*n.countGrowth/480}for(var r in this.operations)if(this.operations.hasOwnProperty(r)){var o=this.operations[r];o.count+=e*o.countGrowth/480}for(var s=0;s=this.automateThreshHigh&&(this.action.name===this.automateActionHigh.name&&this.action.type===this.automateActionHigh.type||(this.action=new q({type:this.automateActionHigh.type,name:this.automateActionHigh.name}),this.startAction(this.action)))),S.routing.isOn(S.Page.Bladeburner)&&this.updateContent()}},J.prototype.calculateMaxStamina=function(){var e=m.a.agility*this.skillMultipliers.effAgi,t=Math.pow(e,.8)+this.staminaBonus;if(t*=this.skillMultipliers.stamina,t*=m.a.bladeburner_max_stamina_mult,isNaN(t))throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");this.maxStamina=t},J.prototype.calculateStaminaGainPerSecond=function(){var e=m.a.agility*this.skillMultipliers.effAgi;return(.0085+this.maxStamina/7e4)*Math.pow(e,.17)*(this.skillMultipliers.stamina*m.a.bladeburner_stamina_gain_mult)},J.prototype.calculateStaminaPenalty=function(){return Math.min(1,this.stamina/(.5*this.maxStamina))},J.prototype.changeRank=function(e){if(isNaN(e))throw new Error("NaN passed into Bladeburner.changeRank()");this.rank+=e,this.rank<0&&(this.rank=0),this.maxRank=Math.max(this.rank,this.maxRank);if(Object(c.factionExists)("Bladeburners")){var t=c.Factions.Bladeburners;if(!(t instanceof l.Faction))throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button");if(t.isMember){var n=1+t.favor/100;t.playerReputation+=2*e*m.a.faction_rep_mult*n}}var i=3*(this.totalSkillPoints+1);if(this.maxRank>=i){var a=Math.floor((this.maxRank-i)/3+1);this.skillPoints+=a,this.totalSkillPoints+=a}},J.prototype.getCurrentCity=function(){var e=this.cities[this.city];if(!(e instanceof j))throw new Error("Bladeburner.getCurrentCity() did not properly return a City object");return e},J.prototype.resetSkillMultipliers=function(){this.skillMultipliers={successChanceAll:1,successChanceStealth:1,successChanceKill:1,successChanceContract:1,successChanceOperation:1,successChanceEstimate:1,actionTime:1,effHack:1,effStr:1,effDef:1,effDex:1,effAgi:1,effCha:1,effInt:1,stamina:1,money:1,expGain:1,weaponAbility:1,gunAbility:1}},J.prototype.updateSkillMultipliers=function(){for(var e in this.resetSkillMultipliers(),this.skills)if(this.skills.hasOwnProperty(e)){var t=F[e];if(null==t)throw new Error("Could not find Skill Object for: "+e);var n=this.skills[e];if(null==n||n<=0)continue;for(var i=Object.keys(this.skillMultipliers),a=0;a=this.actionTimeToComplete?this.completeAction():void 0}},J.prototype.completeAction=function(){switch(this.action.type){case K.Contract:case K.Operation:try{var e=this.action.type===K.Operation;if(null==(p=this.getActionObject(this.action)))throw new Error("Failed to get Contract/Operation Object for: "+this.action.name);var t=p.getDifficulty(),n=Math.pow(t,.28)+t/650,i=Math.pow(p.rewardFac,p.level-1);if(this.stamina-=.285*n,this.stamina<0&&(this.stamina=0),p.attempt(this)){this.gainActionStats(p,!0),++p.successes,--p.count;var a=0;if(e||(a=25e4*i*this.skillMultipliers.money,m.a.gainMoney(a),m.a.recordMoneySource(a,"bladeburner")),e?p.setMaxLevel(2.5):p.setMaxLevel(3),p.rankGain){var s=Object(v.addOffset)(p.rankGain*i*r.BitNodeMultipliers.BladeburnerRank,10);this.changeRank(s),e&&this.logging.ops?this.log(p.name+" successfully completed! Gained "+Object(P.formatNumber)(s,3)+" rank"):!e&&this.logging.contracts&&this.log(p.name+" contract successfully completed! Gained "+Object(P.formatNumber)(s,3)+" rank and "+h.numeralWrapper.format(a,"$0.000a"))}e?this.completeOperation(!0):this.completeContract(!0)}else{this.gainActionStats(p,!1),++p.failures;var l=0,c=0;p.rankLoss&&(l=Object(v.addOffset)(p.rankLoss*i,10),this.changeRank(-1*l)),p.hpLoss&&(c=p.hpLoss*n,c=Math.ceil(Object(v.addOffset)(c,10)),this.hpLost+=c,m.a.takeDamage(c)&&(++this.numHosp,this.moneyLost+=o.CONSTANTS.HospitalCostPerHp*m.a.max_hp));var u="";l>0&&(u+="Lost "+Object(P.formatNumber)(l,3)+" rank."),c>0&&(u+="Took "+Object(P.formatNumber)(c,0)+" damage."),e&&this.logging.ops?this.log(p.name+" failed! "+u):!e&&this.logging.contracts&&this.log(p.name+" contract failed! "+u),e?this.completeOperation(!1):this.completeContract(!1)}p.autoLevel&&(p.level=p.maxLevel),this.startAction(this.action)}catch(e){Object(M.exceptionAlert)(e)}break;case K.BlackOp:case K.BlackOperation:try{var p;if(null==(p=this.getActionObject(this.action))||!(p instanceof z))throw new Error("Failed to get BlackOperation Object for: "+this.action.name);t=p.getDifficulty(),n=Math.pow(t,.28)+t/650;this.stamina-=.285*n,this.stamina<0&&(this.stamina=0);var g,_=p.teamCount;if(p.attempt(this)){this.gainActionStats(p,!0),p.count=0,this.blackops[p.name]=!0;var y=0;if(p.rankGain&&(y=Object(v.addOffset)(p.rankGain*r.BitNodeMultipliers.BladeburnerRank,10),this.changeRank(y)),g=Math.ceil(_/2),"Operation Daedalus"===p.name)return this.resetAction(),Object(d.a)(m.a.bitNodeN);S.routing.isOn(S.Page.Bladeburner)&&this.createActionAndSkillsContent(),this.logging.blackops&&this.log(p.name+" successful! Gained "+Object(P.formatNumber)(y,1)+" rank")}else{this.gainActionStats(p,!1);var f=0;c=0;p.rankLoss&&(f=Object(v.addOffset)(p.rankLoss,10),this.changeRank(-1*f)),p.hpLoss&&(c=p.hpLoss*n,c=Math.ceil(Object(v.addOffset)(c,10)),m.a.takeDamage(c)&&(++this.numHosp,this.moneyLost+=o.CONSTANTS.HospitalCostPerHp*m.a.max_hp)),g=Math.floor(_),this.logging.blackops&&this.log(p.name+" failed! Lost "+Object(P.formatNumber)(f,1)+" rank and took "+Object(P.formatNumber)(c,0)+" damage")}if(this.resetAction(),_>=1){var b=Object(A.getRandomInt)(1,g);this.teamSize-=b,this.teamLost+=b,this.logging.blackops&&this.log("You lost "+Object(P.formatNumber)(b,0)+" team members during "+p.name)}}catch(e){Object(M.exceptionAlert)(e)}break;case K.Training:this.stamina-=.1425;var E=30*m.a.strength_exp_mult,k=30*m.a.defense_exp_mult,C=30*m.a.dexterity_exp_mult,O=30*m.a.agility_exp_mult,T=.04*this.skillMultipliers.stamina;m.a.gainStrengthExp(E),m.a.gainDefenseExp(k),m.a.gainDexterityExp(C),m.a.gainAgilityExp(O),this.staminaBonus+=T,this.logging.general&&this.log("Training completed. Gained: "+Object(P.formatNumber)(E,1)+" str exp, "+Object(P.formatNumber)(k,1)+" def exp, "+Object(P.formatNumber)(C,1)+" dex exp, "+Object(P.formatNumber)(O,1)+" agi exp, "+Object(P.formatNumber)(T,3)+" max stamina"),this.startAction(this.action);break;case K.FieldAnalysis:case K["Field Analysis"]:var w=.04*Math.pow(m.a.hacking_skill,.3)+.04*Math.pow(m.a.intelligence,.9)+.02*Math.pow(m.a.charisma,.3);if(w*=m.a.bladeburner_analysis_mult,isNaN(w)||w<0)throw new Error("Field Analysis Effectiveness calculated to be NaN or negative");var x=20*m.a.hacking_exp_mult,R=20*m.a.charisma_exp_mult;m.a.gainHackingExp(x),m.a.gainIntelligenceExp(.001),m.a.gainCharismaExp(R),this.changeRank(.1*r.BitNodeMultipliers.BladeburnerRank),this.getCurrentCity().improvePopulationEstimateByPercentage(w*this.skillMultipliers.successChanceEstimate),this.logging.general&&this.log("Field analysis completed. Gained 0.1 rank, "+Object(P.formatNumber)(x,1)+" hacking exp, and "+Object(P.formatNumber)(R,1)+" charisma exp"),this.startAction(this.action);break;case K.Recruitment:var N=this.getRecruitmentSuccessChance();if(Math.random()=1){n=e?Math.ceil(i/2):Math.floor(i);var a=Object(A.getRandomInt)(0,n);this.teamSize-=a,this.teamLost+=a,this.logging.ops&&a>0&&this.log("Lost "+Object(P.formatNumber)(a,0)+" team members during this "+t.name)}var r=this.getCurrentCity();switch(t.name){case"Investigation":e?(r.improvePopulationEstimateByPercentage(.4*this.skillMultipliers.successChanceEstimate),Math.random()<.02*this.skillMultipliers.successChanceEstimate&&r.improveCommunityEstimate(1)):this.triggerPotentialMigration(this.city,.1);break;case"Undercover Operation":e?(r.improvePopulationEstimateByPercentage(.8*this.skillMultipliers.successChanceEstimate),Math.random()<.02*this.skillMultipliers.successChanceEstimate&&r.improveCommunityEstimate(1)):this.triggerPotentialMigration(this.city,.15);break;case"Sting Operation":e&&r.changePopulationByPercentage(-.1,{changeEstEqually:!0,nonZero:!0}),r.changeChaosByCount(.1);break;case"Raid":if(e)r.changePopulationByPercentage(-1,{changeEstEqually:!0,nonZero:!0}),--r.comms,--r.commsEst;else{var o=Object(A.getRandomInt)(-10,-5)/10;r.changePopulationByPercentage(o,{nonZero:!0})}r.changeChaosByPercentage(Object(A.getRandomInt)(1,5));break;case"Stealth Retirement Operation":e&&r.changePopulationByPercentage(-.5,{changeEstEqually:!0,nonZero:!0}),r.changeChaosByPercentage(Object(A.getRandomInt)(-3,-1));break;case"Assassination":e&&r.changePopulationByCount(-1,{estChange:-1}),r.changeChaosByPercentage(Object(A.getRandomInt)(-5,5));break;default:throw new Error("Invalid Action name in completeOperation: "+this.action.name)}},J.prototype.getRecruitmentTime=function(){var e=m.a.charisma*this.skillMultipliers.effCha,t=Math.pow(e,.81)+e/90;return Math.max(10,Math.round(300-t))},J.prototype.getRecruitmentSuccessChance=function(){return Math.pow(m.a.charisma,.45)/(this.teamSize+1)},J.prototype.getDiplomacyEffectiveness=function(){return(100-(Math.pow(m.a.charisma,.045)+m.a.charisma/1e3))/100},J.prototype.gainActionStats=function(e,t){var n=e.getDifficulty(),i=Math.pow(n,.28)+n/650,a=this.actionTimeToComplete,r=t?1:.5,o=1*a*r*i,s=.001*a*r*i;const l=this.skillMultipliers.expGain;m.a.gainHackingExp(o*e.weights.hack*m.a.hacking_exp_mult*l),m.a.gainStrengthExp(o*e.weights.str*m.a.strength_exp_mult*l),m.a.gainDefenseExp(o*e.weights.def*m.a.defense_exp_mult*l),m.a.gainDexterityExp(o*e.weights.dex*m.a.dexterity_exp_mult*l),m.a.gainAgilityExp(o*e.weights.agi*m.a.agility_exp_mult*l),m.a.gainCharismaExp(o*e.weights.cha*m.a.charisma_exp_mult*l),m.a.gainIntelligenceExp(s*e.weights.int*l)},J.prototype.randomEvent=function(){var e=Math.random(),t=N[Object(A.getRandomInt)(0,5)],n=this.cities[t];if(!(n instanceof j))throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()");for(var i=N[Object(A.getRandomInt)(0,5)];i===t;)i=N[Object(A.getRandomInt)(0,5)];var a=this.cities[i];if(!(n instanceof j&&a instanceof j))throw new Error("sourceCity/destCity was not a City object in Bladeburner.randomEvent()");if(e<=.05){++n.comms;var r=Object(A.getRandomInt)(10,20)/100,o=Math.round(n.pop*r);n.pop+=o,this.logging.events&&this.log("Intelligence indicates that a new Synthoid community was formed in a city")}else if(e<=.1)if(n.comms<=0){++n.comms;r=Object(A.getRandomInt)(10,20)/100,o=Math.round(n.pop*r);n.pop+=o,this.logging.events&&this.log("Intelligence indicates that a new Synthoid community was formed in a city")}else{--n.comms,++a.comms;r=Object(A.getRandomInt)(10,20)/100,o=Math.round(n.pop*r);n.pop-=o,a.pop+=o,this.logging.events&&this.log("Intelligence indicates that a Synthoid community migrated from "+t+" to some other city")}else if(e<=.3){r=Object(A.getRandomInt)(8,24)/100,o=Math.round(n.pop*r);n.pop+=o,this.logging.events&&this.log("Intelligence indicates that the Synthoid population of "+t+" just changed significantly")}else if(e<=.5)this.triggerMigration(t),this.logging.events&&this.log("Intelligence indicates that a large number of Synthoids migrated from "+t+" to some other city");else if(e<=.7)n.chaos+=1,n.chaos*=1+Object(A.getRandomInt)(5,20)/100,this.logging.events&&this.log("Tensions between Synthoids and humans lead to riots in "+t+"! Chaos increased");else if(e<=.9){r=Object(A.getRandomInt)(8,20)/100,o=Math.round(n.pop*r);n.pop-=o,this.logging.events&&this.log("Intelligence indicates that the Synthoid population of "+t+" just changed significantly")}},J.prototype.triggerPotentialMigration=function(e,t){(null==t||isNaN(t))&&console.log("ERROR: Invalid 'chance' parameter passed into Bladeburner.triggerPotentialMigration()"),t>1&&(t/=100),Math.random()0&&(r*=Object(A.getRandomInt)(2,4),--i.comms,++n.comms);var o=Math.round(i.pop*r);i.pop-=o,n.pop+=o};var X={};J.prototype.initializeDomElementRefs=function(){X={bladeburnerDiv:null,overviewConsoleParentDiv:null,overviewDiv:null,actionAndSkillsDiv:null,currentTab:null,consoleDiv:null,consoleTable:null,consoleInputRow:null,consoleInputCell:null,consoleInputHeader:null,consoleInput:null,overviewRank:null,overviewStamina:null,overviewStaminaHelpTip:null,overviewGen1:null,overviewEstPop:null,overviewEstPopHelpTip:null,overviewEstComms:null,overviewChaos:null,overviewSkillPoints:null,overviewBonusTime:null,overviewAugSuccessMult:null,overviewAugMaxStaminaMult:null,overviewAugStaminaGainMult:null,overviewAugAnalysisMult:null,actionsAndSkillsDesc:null,actionsAndSkillsList:null,generalActions:{},contracts:{},operations:{},blackops:{},skills:{},skillPointsDisplay:null}},J.prototype.createContent=function(){if(X.bladeburnerDiv=Object(O.createElement)("div",{id:"bladeburner-container",position:"fixed",class:"generic-menupage-container"}),X.overviewConsoleParentDiv=Object(O.createElement)("div",{height:"60%",display:"block",position:"relative"}),X.overviewDiv=Object(O.createElement)("div",{width:"30%",display:"inline-block",border:"1px solid white"}),X.actionAndSkillsDiv=Object(O.createElement)("div",{height:"60%",width:"70%",display:"block",border:"1px solid white",margin:"6px",padding:"6px"}),X.currentTab="general",this.createOverviewContent(),this.createActionAndSkillsContent(),X.consoleDiv=Object(O.createElement)("div",{class:"bladeburner-console-div",clickListener:()=>(X.consoleInput instanceof Element&&X.consoleInput.focus(),!1)}),X.consoleTable=Object(O.createElement)("table",{class:"bladeburner-console-table"}),X.consoleInputRow=Object(O.createElement)("tr",{class:"bladeburner-console-input-row",id:"bladeubrner-console-input-row"}),X.consoleInputCell=Object(O.createElement)("td",{class:"bladeburner-console-input-cell"}),X.consoleInputHeader=Object(O.createElement)("pre",{innerText:"> "}),X.consoleInput=Object(O.createElement)("input",{type:"text",class:"bladeburner-console-input",tabIndex:1,onfocus:()=>{X.consoleInput.value=X.consoleInput.value}}),X.consoleInputCell.appendChild(X.consoleInputHeader),X.consoleInputCell.appendChild(X.consoleInput),X.consoleInputRow.appendChild(X.consoleInputCell),X.consoleTable.appendChild(X.consoleInputRow),X.consoleDiv.appendChild(X.consoleTable),X.overviewConsoleParentDiv.appendChild(X.overviewDiv),X.overviewConsoleParentDiv.appendChild(X.consoleDiv),X.bladeburnerDiv.appendChild(X.overviewConsoleParentDiv),X.bladeburnerDiv.appendChild(X.actionAndSkillsDiv),document.getElementById("entire-game-container").appendChild(X.bladeburnerDiv),0===this.consoleLogs.length)this.postToConsole("Bladeburner Console BETA"),this.postToConsole("Type 'help' to see console commands");else for(let e=0;e{Object(f.dialogBoxCreate)("Performing actions will use up your stamina.

Your max stamina is determined primarily by your agility stat.

Your stamina gain rate is determined by both your agility and your max stamina. Higher max stamina leads to a higher gain rate.

Once your stamina falls below 50% of its max value, it begins to negatively affect the success rate of your contracts/operations. This penalty is shown in the overview panel. If the penalty is 15%, then this means your success rate would be multipled by 85% (100 - 15).

Your max stamina and stamina gain rate can also be increased by training, or through skills and Augmentation upgrades.")}}),X.overviewGen1=Object(O.createElement)("p",{display:"block"}),X.overviewEstPop=Object(O.createElement)("p",{innerText:"Est. Synthoid Population: ",display:"inline-block",tooltip:"This is your Bladeburner division's estimate of how many Synthoids exist in your current city."}),X.overviewEstPopHelpTip=Object(O.createElement)("div",{innerText:"?",class:"help-tip",clickListener:()=>{Object(f.dialogBoxCreate)("The success rate of your contracts/operations depends on the population of Synthoids in your current city. The success rate that is shown to you is only an estimate, and it is based on your Synthoid population estimate.

Therefore, it is important that this Synthoid population estimate is accurate so that you have a better idea of your success rate for contracts/operations. Certain actions will increase the accuracy of your population estimate.

The Synthoid populations of cities can change due to your actions or random events. If random events occur, they will be logged in the Bladeburner Console.")}}),X.overviewEstComms=Object(O.createElement)("p",{innerText:"Est. Synthoid Communities: ",display:"inline-block",tooltip:"This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city."}),X.overviewChaos=Object(O.createElement)("p",{innerText:"City Chaos: ",display:"inline-block",tooltip:"The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a chaos level can make contracts and operations harder."}),X.overviewBonusTime=Object(O.createElement)("p",{innerText:"Bonus time: ",display:"inline-block",tooltip:"You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by browser). Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed."}),X.overviewSkillPoints=Object(O.createElement)("p",{display:"block"}),X.overviewAugSuccessMult=Object(O.createElement)("p",{display:"block"}),X.overviewAugMaxStaminaMult=Object(O.createElement)("p",{display:"block"}),X.overviewAugStaminaGainMult=Object(O.createElement)("p",{display:"block"}),X.overviewAugAnalysisMult=Object(O.createElement)("p",{display:"block"}),X.overviewDiv.appendChild(X.overviewRank),Object(k.appendLineBreaks)(X.overviewDiv,1),X.overviewDiv.appendChild(X.overviewStamina),X.overviewDiv.appendChild(X.overviewStaminaHelpTip),X.overviewDiv.appendChild(X.overviewGen1),X.overviewDiv.appendChild(X.overviewEstPop),X.overviewDiv.appendChild(X.overviewEstPopHelpTip),Object(k.appendLineBreaks)(X.overviewDiv,1),X.overviewDiv.appendChild(X.overviewEstComms),Object(k.appendLineBreaks)(X.overviewDiv,1),X.overviewDiv.appendChild(X.overviewChaos),Object(k.appendLineBreaks)(X.overviewDiv,2),X.overviewDiv.appendChild(X.overviewBonusTime),X.overviewDiv.appendChild(X.overviewSkillPoints),Object(k.appendLineBreaks)(X.overviewDiv,1),X.overviewDiv.appendChild(X.overviewAugSuccessMult),X.overviewDiv.appendChild(X.overviewAugMaxStaminaMult),X.overviewDiv.appendChild(X.overviewAugStaminaGainMult),X.overviewDiv.appendChild(X.overviewAugAnalysisMult),Object(k.appendLineBreaks)(X.overviewDiv,1),X.overviewDiv.appendChild(Object(O.createElement)("a",{innerHTML:"Travel",class:"a-link-button",display:"inline-block",clickListener:()=>{var e="bladeburner-travel-popup-cancel-btn",t=[];t.push(Object(O.createElement)("a",{innerText:"Cancel",class:"a-link-button",clickListener:()=>(Object(R.removeElementById)(e),!1)})),t.push(Object(O.createElement)("p",{innerText:"Travel to a different city for your Bladeburner activities. This does not cost any money. The city you are in for your Bladeburner duties does not affect your location in the game otherwise"}));for(var n=0;n(n.city=N[i],Object(R.removeElementById)(e),n.updateOverviewContent(),!1)}))}(this,n);Object(T.createPopup)(e,t)}}));if(Object(c.factionExists)("Bladeburners")){var e=c.Factions.Bladeburners;if(!(e instanceof l.Faction))throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button");X.overviewDiv.appendChild(Object(O.createElement)("a",{innerText:"Faction",class:"a-link-button",display:"inline-block",tooltip:"Apply to the Bladeburner Faction, or go to the faction page if you are already a member",clickListener:()=>(e.isMember?(s.Engine.loadFactionContent(),Object(u.a)("Bladeburners")):this.rank>=25?(Object(u.c)(e),Object(f.dialogBoxCreate)("Congratulations! You were accepted into the Bladeburners faction"),Object(b.removeChildrenFromElement)(X.overviewDiv),this.createOverviewContent()):Object(f.dialogBoxCreate)("You need a rank of 25 to join the Bladeburners Faction!"),!1)}))}X.overviewDiv.appendChild(Object(O.createElement)("br")),X.overviewDiv.appendChild(Object(O.createElement)("br")),this.updateOverviewContent()},J.prototype.createActionAndSkillsContent=function(){null==X.currentTab&&(X.currentTab="general"),Object(b.removeChildrenFromElement)(X.actionAndSkillsDiv),Object(C.clearObject)(X.generalActions),Object(C.clearObject)(X.contracts),Object(C.clearObject)(X.operations),Object(C.clearObject)(X.blackops),Object(C.clearObject)(X.skills);for(var e=X.currentTab.toLowerCase(),t=["General","Contracts","Operations","BlackOps","Skills"],n=0;n(X.currentTab=e[t].toLowerCase(),n.createActionAndSkillsContent(),!1)}))}(t,n,this,e);switch(X.actionsAndSkillsDesc=Object(O.createElement)("p",{display:"block",margin:"4px",padding:"4px"}),Object(b.removeChildrenFromElement)(X.actionsAndSkillsList),X.actionsAndSkillsList=Object(O.createElement)("ul"),e){case"general":this.createGeneralActionsContent();break;case"contracts":this.createContractsContent();break;case"operations":this.createOperationsContent();break;case"blackops":this.createBlackOpsContent();break;case"skills":this.createSkillsContent();break;default:throw new Error("Invalid value for DomElems.currentTab in Bladeburner.createActionAndSkillsContent")}this.updateContent(),X.actionAndSkillsDiv.appendChild(X.actionsAndSkillsDesc),X.actionAndSkillsDiv.appendChild(X.actionsAndSkillsList)},J.prototype.createGeneralActionsContent=function(){if(null==X.actionsAndSkillsList||null==X.actionsAndSkillsDesc)throw new Error("Bladeburner.createGeneralActionsContent called with either DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");for(var e in X.actionsAndSkillsDesc.innerText="These are generic actions that will assist you in your Bladeburner duties. They will not affect your Bladeburner rank in any way.",G)G.hasOwnProperty(e)&&(X.generalActions[e]=Object(O.createElement)("div",{class:"bladeburner-action",name:e}),X.actionsAndSkillsList.appendChild(X.generalActions[e]))},J.prototype.createContractsContent=function(){if(null==X.actionsAndSkillsList||null==X.actionsAndSkillsDesc)throw new Error("Bladeburner.createContractsContent called with either DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");for(var e in X.actionsAndSkillsDesc.innerHTML="Complete contracts in order to increase your Bladeburner rank and earn money. Failing a contract will cause you to lose HP, which can lead to hospitalization.

You can unlock higher-level contracts by successfully completing them. Higher-level contracts are more difficult, but grant more rank, experience, and money.",this.contracts)this.contracts.hasOwnProperty(e)&&(X.contracts[e]=Object(O.createElement)("div",{class:"bladeburner-action",name:e}),X.actionsAndSkillsList.appendChild(X.contracts[e]))},J.prototype.createOperationsContent=function(){if(null==X.actionsAndSkillsList||null==X.actionsAndSkillsDesc)throw new Error("Bladeburner.createOperationsContent called with either DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");for(var e in X.actionsAndSkillsDesc.innerHTML="Carry out operations for the Bladeburner division. Failing an operation will reduce your Bladeburner rank. It will also cause you to lose HP, which can lead to hospitalization. In general, operations are harder and more punishing than contracts, but are also more rewarding.

Operations can affect the chaos level and Synthoid population of your current city. The exact effects vary between different Operations.

For operations, you can use a team. You must first recruit team members. Having a larger team will improves your chances of success.

You can unlock higher-level operations by successfully completing them. Higher-level operations are more difficult, but grant more rank and experience.",this.operations)this.operations.hasOwnProperty(e)&&(X.operations[e]=Object(O.createElement)("div",{class:"bladeburner-action",name:e}),X.actionsAndSkillsList.appendChild(X.operations[e]))},J.prototype.createBlackOpsContent=function(){if(null==X.actionsAndSkillsList||null==X.actionsAndSkillsDesc)throw new Error("Bladeburner.createBlackOpsContent called with either DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");X.actionsAndSkillsDesc.innerHTML="Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked successively by completing the one before it.

Your ultimate goal to climb through the ranks of Bladeburners is to complete all of the Black Ops.

Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank losses.";var e=[];for(var t in V)V.hasOwnProperty(t)&&e.push(V[t]);e.sort(function(e,t){return e.reqdRank-t.reqdRank});for(var n=0;n
Note that when upgrading a skill, the benefit for that skill is additive. However, the effects of different skills with each other is multiplicative.

";for(var e=Object.keys(this.skillMultipliers),t=0;t";break;case"successChanceStealth":X.actionsAndSkillsDesc.innerHTML+="Stealth Success Chance: x"+n+"
";break;case"successChanceKill":X.actionsAndSkillsDesc.innerHTML+="Retirement Success Chance: x"+n+"
";break;case"successChanceContract":X.actionsAndSkillsDesc.innerHTML+="Contract Success Chance: x"+n+"
";break;case"successChanceOperation":X.actionsAndSkillsDesc.innerHTML+="Operation Success Chance: x"+n+"
";break;case"successChanceEstimate":X.actionsAndSkillsDesc.innerHTML+="Synthoid Data Estimate: x"+n+"
";break;case"actionTime":X.actionsAndSkillsDesc.innerHTML+="Action Time: x"+n+"
";break;case"effHack":X.actionsAndSkillsDesc.innerHTML+="Hacking Skill: x"+n+"
";break;case"effStr":X.actionsAndSkillsDesc.innerHTML+="Strength: x"+n+"
";break;case"effDef":X.actionsAndSkillsDesc.innerHTML+="Defense: x"+n+"
";break;case"effDex":X.actionsAndSkillsDesc.innerHTML+="Dexterity: x"+n+"
";break;case"effAgi":X.actionsAndSkillsDesc.innerHTML+="Agility: x"+n+"
";break;case"effCha":X.actionsAndSkillsDesc.innerHTML+="Charisma: x"+n+"
";break;case"effInt":X.actionsAndSkillsDesc.innerHTML+="Intelligence: x"+n+"
";break;case"stamina":X.actionsAndSkillsDesc.innerHTML+="Stamina: x"+n+"
";break;case"money":X.actionsAndSkillsDesc.innerHTML+="Contract Money: x"+n+"
";break;case"expGain":X.actionsAndSkillsDesc.innerHTML+="Exp Gain: x"+n+"
";break;case"weaponAbility":case"gunAbility":break;default:console.log("Warning: Unrecognized SkillMult Key: "+e[t])}}for(var i in X.skillPointsDisplay=Object(O.createElement)("p",{innerHTML:"
Skill Points: "+Object(P.formatNumber)(this.skillPoints,0)+""}),X.actionAndSkillsDiv.appendChild(X.skillPointsDisplay),F)F.hasOwnProperty(i)&&(X.skills[i]=Object(O.createElement)("div",{class:"bladeburner-action",name:i}),X.actionsAndSkillsList.appendChild(X.skills[i]))},J.prototype.updateContent=function(){this.updateOverviewContent(),this.updateActionAndSkillsContent()},J.prototype.updateOverviewContent=function(){S.routing.isOn(S.Page.Bladeburner)&&(X.overviewRank.childNodes[0].nodeValue="Rank: "+Object(P.formatNumber)(this.rank,2),X.overviewStamina.innerText="Stamina: "+Object(P.formatNumber)(this.stamina,3)+" / "+Object(P.formatNumber)(this.maxStamina,3),X.overviewGen1.innerHTML="Stamina Penalty: "+Object(P.formatNumber)(100*(1-this.calculateStaminaPenalty()),1)+"%

Team Size: "+Object(P.formatNumber)(this.teamSize,0)+"
Team Members Lost: "+Object(P.formatNumber)(this.teamLost,0)+"

Num Times Hospitalized: "+this.numHosp+"
Money Lost From Hospitalizations: "+h.numeralWrapper.format(this.moneyLost,"$0.000a")+"

Current City: "+this.city+"
",X.overviewEstPop.childNodes[0].nodeValue="Est. Synthoid Population: "+h.numeralWrapper.format(this.getCurrentCity().popEst,"0.000a"),X.overviewEstComms.childNodes[0].nodeValue="Est. Synthoid Communities: "+Object(P.formatNumber)(this.getCurrentCity().comms,0),X.overviewChaos.childNodes[0].nodeValue="City Chaos: "+Object(P.formatNumber)(this.getCurrentCity().chaos),X.overviewSkillPoints.innerText="Skill Points: "+Object(P.formatNumber)(this.skillPoints,0),X.overviewBonusTime.childNodes[0].nodeValue="Bonus time: "+this.storedCycles/5,X.overviewAugSuccessMult.innerText="Aug. Success Chance Mult: "+Object(P.formatNumber)(100*m.a.bladeburner_success_chance_mult,1)+"%",X.overviewAugMaxStaminaMult.innerText="Aug. Max Stamina Mult: "+Object(P.formatNumber)(100*m.a.bladeburner_max_stamina_mult,1)+"%",X.overviewAugStaminaGainMult.innerText="Aug. Stamina Gain Mult: "+Object(P.formatNumber)(100*m.a.bladeburner_stamina_gain_mult,1)+"%",X.overviewAugAnalysisMult.innerText="Aug. Field Analysis Mult: "+Object(P.formatNumber)(100*m.a.bladeburner_analysis_mult,1)+"%")},J.prototype.updateActionAndSkillsContent=function(){switch(null==X.currentTab&&(X.currentTab="general"),X.currentTab.toLowerCase()){case"general":for(var e=Object.keys(X.generalActions),t=0;t";var h=Object.keys(X.skills);for(t=0;t(this.action.type=K[t.name],this.action.name=t.name,this.startAction(this.action),this.updateActionAndSkillsContent(),!1)}));Object(k.appendLineBreaks)(e,2),e.appendChild(Object(O.createElement)("pre",{innerHTML:t.desc,display:"inline-block"}))},J.prototype.updateContractsUIElement=function(e,t){Object(b.removeChildrenFromElement)(e);var n=e.classList.contains(L),i=t.getSuccessChance(this,{est:!0});if(e.appendChild(Object(O.createElement)("h2",{innerText:n?t.name+" (IN PROGRESS - "+Object(P.formatNumber)(this.actionTimeCurrent,0)+" / "+Object(P.formatNumber)(this.actionTimeToComplete,0)+")":t.name,display:"inline-block"})),n){var a=this.actionTimeCurrent/this.actionTimeToComplete;e.appendChild(Object(O.createElement)("p",{display:"block",innerText:Object(y.createProgressBarText)({progress:a})}))}else e.appendChild(Object(O.createElement)("a",{innerText:"Start",class:"a-link-button",padding:"3px",margin:"3px",clickListener:()=>(this.action.type=K.Contract,this.action.name=t.name,this.startAction(this.action),this.updateActionAndSkillsContent(),!1)}));var r=t.level>=t.maxLevel;Object(k.appendLineBreaks)(e,2),e.appendChild(Object(O.createElement)("pre",{display:"inline-block",innerText:"Level: "+t.level+" / "+t.maxLevel,tooltip:t.getSuccessesNeededForNextLevel(3)+" successes needed for next level"})),e.appendChild(Object(O.createElement)("a",{class:r?"a-link-button-inactive":"a-link-button",innerHTML:"↑",padding:"2px",margin:"2px",tooltip:n?"WARNING: changing the level will restart the contract":"",display:"inline",clickListener:()=>(++t.level,n&&this.startAction(this.action),this.updateContractsUIElement(e,t),!1)})),e.appendChild(Object(O.createElement)("a",{class:t.level<=1?"a-link-button-inactive":"a-link-button",innerHTML:"↓",padding:"2px",margin:"2px",tooltip:n?"WARNING: changing the level will restart the contract":"",display:"inline",clickListener:()=>(--t.level,n&&this.startAction(this.action),this.updateContractsUIElement(e,t),!1)}));var o=t.getActionTime(this);Object(k.appendLineBreaks)(e,2),e.appendChild(Object(O.createElement)("pre",{display:"inline-block",innerHTML:t.desc+"\n\nEstimated success chance: "+Object(P.formatNumber)(100*i,1)+"%\nTime Required (s): "+Object(P.formatNumber)(o,0)+"\nContracts remaining: "+Math.floor(t.count)+"\nSuccesses: "+t.successes+"\nFailures: "+t.failures})),e.appendChild(Object(O.createElement)("br"));var s="bladeburner-"+t.name+"-autolevel-checkbox";e.appendChild(Object(O.createElement)("label",{for:s,innerText:"Autolevel",color:"white",tooltip:"Automatically increase contract level when possible"}));var l=Object(O.createElement)("input",{type:"checkbox",id:s,margin:"4px",checked:t.autoLevel,changeListener:()=>{t.autoLevel=l.checked}});e.appendChild(l)},J.prototype.updateOperationsUIElement=function(e,t){Object(b.removeChildrenFromElement)(e);var n=e.classList.contains(L),i=t.getSuccessChance(this,{est:!0});if(e.appendChild(Object(O.createElement)("h2",{innerText:n?t.name+" (IN PROGRESS - "+Object(P.formatNumber)(this.actionTimeCurrent,0)+" / "+Object(P.formatNumber)(this.actionTimeToComplete,0)+")":t.name,display:"inline-block"})),n){var a=this.actionTimeCurrent/this.actionTimeToComplete;e.appendChild(Object(O.createElement)("p",{display:"block",innerText:Object(y.createProgressBarText)({progress:a})}))}else e.appendChild(Object(O.createElement)("a",{innerText:"Start",class:"a-link-button",margin:"3px",padding:"3px",clickListener:()=>(this.action.type=K.Operation,this.action.name=t.name,this.startAction(this.action),this.updateActionAndSkillsContent(),!1)})),e.appendChild(Object(O.createElement)("a",{innerText:"Set Team Size (Curr Size: "+Object(P.formatNumber)(t.teamCount,0)+")",class:"a-link-button",margin:"3px",padding:"3px",clickListener:()=>{var n="bladeburner-operation-set-team-size-popup",i=Object(O.createElement)("p",{innerText:"Enter the amount of team members you would like to take on these operations. If you do not have the specified number of team members, then as many as possible will be used. Note that team members may be lost during operations."}),a=Object(O.createElement)("input",{type:"number",placeholder:"Team Members"}),r=Object(O.createElement)("a",{innerText:"Confirm",class:"a-link-button",clickListener:()=>{var i=Math.round(parseFloat(a.value));return isNaN(i)?Object(f.dialogBoxCreate)("Invalid value entered for number of Team Members (must be numeric)"):(t.teamCount=i,this.updateOperationsUIElement(e,t)),Object(R.removeElementById)(n),!1}}),o=Object(O.createElement)("a",{innerText:"Cancel",class:"a-link-button",clickListener:()=>(Object(R.removeElementById)(n),!1)});Object(T.createPopup)(n,[i,a,r,o])}}));var r=t.level>=t.maxLevel;Object(k.appendLineBreaks)(e,2),e.appendChild(Object(O.createElement)("pre",{display:"inline-block",innerText:"Level: "+t.level+" / "+t.maxLevel,tooltip:t.getSuccessesNeededForNextLevel(2.5)+" successes needed for next level"})),e.appendChild(Object(O.createElement)("a",{class:r?"a-link-button-inactive":"a-link-button",innerHTML:"↑",padding:"2px",margin:"2px",tooltip:n?"WARNING: changing the level will restart the Operation":"",display:"inline",clickListener:()=>(++t.level,n&&this.startAction(this.action),this.updateOperationsUIElement(e,t),!1)})),e.appendChild(Object(O.createElement)("a",{class:t.level<=1?"a-link-button-inactive":"a-link-button",innerHTML:"↓",padding:"2px",margin:"2px",tooltip:n?"WARNING: changing the level will restart the Operation":"",display:"inline",clickListener:()=>(--t.level,n&&this.startAction(this.action),this.updateOperationsUIElement(e,t),!1)}));t.getDifficulty();var o=t.getActionTime(this);Object(k.appendLineBreaks)(e,2),e.appendChild(Object(O.createElement)("pre",{display:"inline-block",innerHTML:t.desc+"\n\nEstimated success chance: "+Object(P.formatNumber)(100*i,1)+"%\nTime Required(s): "+Object(P.formatNumber)(o,1)+"\nOperations remaining: "+Math.floor(t.count)+"\nSuccesses: "+t.successes+"\nFailures: "+t.failures})),e.appendChild(Object(O.createElement)("br"));var s="bladeburner-"+t.name+"-autolevel-checkbox";e.appendChild(Object(O.createElement)("label",{for:s,innerText:"Autolevel",color:"white",tooltip:"Automatically increase operation level when possible"}));var l=Object(O.createElement)("input",{type:"checkbox",id:s,margin:"4px",checked:t.autoLevel,changeListener:()=>{t.autoLevel=l.checked}});e.appendChild(l)},J.prototype.updateBlackOpsUIElement=function(e,t){Object(b.removeChildrenFromElement)(e);var n=e.classList.contains(L),i=null!=this.blackops[t.name],a=t.getSuccessChance(this,{est:!0}),r=(t.getDifficulty(),t.getActionTime(this)),o=this.rank>=t.reqdRank;if(i)e.appendChild(Object(O.createElement)("h2",{innerText:t.name+" (COMPLETED)",display:"block"}));else{if(e.appendChild(Object(O.createElement)("h2",{innerText:n?t.name+" (IN PROGRESS - "+Object(P.formatNumber)(this.actionTimeCurrent,0)+" / "+Object(P.formatNumber)(this.actionTimeToComplete,0)+")":t.name,display:"inline-block"})),n){var s=this.actionTimeCurrent/this.actionTimeToComplete;e.appendChild(Object(O.createElement)("p",{display:"block",innerText:Object(y.createProgressBarText)({progress:s})}))}else e.appendChild(Object(O.createElement)("a",{innerText:"Start",margin:"3px",padding:"3px",class:o?"a-link-button":"a-link-button-inactive",clickListener:()=>(this.action.type=K.BlackOperation,this.action.name=t.name,this.startAction(this.action),this.updateActionAndSkillsContent(),!1)})),e.appendChild(Object(O.createElement)("a",{innerText:"Set Team Size (Curr Size: "+Object(P.formatNumber)(t.teamCount,0)+")",class:"a-link-button",margin:"3px",padding:"3px",clickListener:()=>{var n="bladeburner-operation-set-team-size-popup",i=Object(O.createElement)("p",{innerText:"Enter the amount of team members you would like to take on this BlackOp. If you do not have the specified number of team members, then as many as possible will be used. Note that team members may be lost during operations."}),a=Object(O.createElement)("input",{type:"number",placeholder:"Team Members"}),r=Object(O.createElement)("a",{innerText:"Confirm",class:"a-link-button",clickListener:()=>{var i=Math.round(parseFloat(a.value));return isNaN(i)?Object(f.dialogBoxCreate)("Invalid value entered for number of Team Members (must be numeric)"):(t.teamCount=i,this.updateBlackOpsUIElement(e,t)),Object(R.removeElementById)(n),!1}}),o=Object(O.createElement)("a",{innerText:"Cancel",class:"a-link-button",clickListener:()=>(Object(R.removeElementById)(n),!1)});Object(T.createPopup)(n,[i,a,r,o])}}));Object(k.appendLineBreaks)(e,2),e.appendChild(Object(O.createElement)("p",{display:"inline-block",innerHTML:"
"+t.desc+"

"})),e.appendChild(Object(O.createElement)("p",{display:"block",color:o?"white":"red",innerHTML:"Required Rank: "+Object(P.formatNumber)(t.reqdRank,0)+"
"})),e.appendChild(Object(O.createElement)("p",{display:"inline-block",innerHTML:"Estimated Success Chance: "+Object(P.formatNumber)(100*a,1)+"%\nTime Required(s): "+Object(P.formatNumber)(r,1)}))}},J.prototype.updateSkillsUIElement=function(e,t){Object(b.removeChildrenFromElement)(e);var n=t.name,i=0;this.skills[n]&&!isNaN(this.skills[n])&&(i=this.skills[n]);var a=t.calculateCost(i);e.appendChild(Object(O.createElement)("h2",{innerText:t.name+" (Lvl "+i+")",display:"inline-block"}));var r=this.skillPoints>=a,o=!!t.maxLvl&&i>=t.maxLvl;e.appendChild(Object(O.createElement)("a",{innerText:"Level",display:"inline-block",class:r&&!o?"a-link-button":"a-link-button-inactive",margin:"3px",padding:"3px",clickListener:()=>{if(!(this.skillPoints100&&this.consoleLogs.shift()),null!=t&&null!=X.consoleDiv&&(e("#bladeubrner-console-input-row").before(''+t+""),X.consoleTable.childNodes.length>100&&X.consoleTable.removeChild(X.consoleTable.firstChild),this.updateConsoleScroll())},J.prototype.updateConsoleScroll=function(){X.consoleDiv.scrollTop=X.consoleDiv.scrollHeight},J.prototype.resetConsoleInput=function(){X.consoleInput.value=""},J.prototype.clearConsole=function(){for(;X.consoleTable.childNodes.length>1;)X.consoleTable.removeChild(X.consoleTable.firstChild);this.consoleLogs.length=0},J.prototype.log=function(e){this.postToConsole(`[${Object(w.getTimestamp)()}] ${e}`)},J.prototype.executeConsoleCommands=function(e){try{this.consoleHistory[this.consoleHistory.length-1]!=e&&(this.consoleHistory.push(e),this.consoleHistory.length>50&&this.consoleHistory.splice(0,1)),D=this.consoleHistory.length;for(var t=e.split(";"),n=0;n"))}},J.prototype.executeLogConsoleCommand=function(e){if(e.length<3)return this.postToConsole("Invalid usage of log command: log [enable/disable] [action/event]"),void this.postToConsole("Use 'help log' for more details and examples");var t=!0;switch(e[1].toLowerCase().includes("d")&&(t=!1),e[2].toLowerCase()){case"general":case"gen":this.logging.general=t,this.log("Logging "+(t?"enabled":"disabled")+" for general actions");break;case"contract":case"contracts":this.logging.contracts=t,this.log("Logging "+(t?"enabled":"disabled")+" for Contracts");break;case"ops":case"op":case"operations":case"operation":this.logging.ops=t,this.log("Logging "+(t?"enabled":"disabled")+" for Operations");break;case"blackops":case"blackop":case"black operations":case"black operation":this.logging.blackops=t,this.log("Logging "+(t?"enabled":"disabled")+" for BlackOps");break;case"event":case"events":this.logging.events=t,this.log("Logging "+(t?"enabled":"disabled")+" for events");break;case"all":this.logging.general=t,this.logging.contracts=t,this.logging.ops=t,this.logging.blackops=t,this.logging.events=t,this.log("Logging "+(t?"enabled":"disabled")+" for everything");break;default:this.postToConsole("Invalid action/event type specified: "+e[2]),this.postToConsole("Examples of valid action/event identifiers are: [general, contracts, ops, blackops, events]")}},J.prototype.executeSkillConsoleCommand=function(e){switch(e.length){case 1:this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"),this.postToConsole("Use 'help skill' for more info");break;case 2:if("list"===e[1].toLowerCase()){this.postToConsole("Skills: ");for(var t=Object.keys(F),n=0;n=c?(this.skillPoints-=c,this.upgradeSkill(i),this.log(i.name+" upgraded to Level "+this.skills[s]),this.createActionAndSkillsContent()):this.postToConsole("You do not have enough Skill Points to upgrade this. You need "+Object(P.formatNumber)(c,0))}else this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"),this.postToConsole("Use 'help skill' for more info");break;default:this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"),this.postToConsole("Use 'help skill' for more info")}},J.prototype.executeStartConsoleCommand=function(e){if(3!==e.length)return this.postToConsole("Invalid usage of 'start' console command: start [type] [name]"),void this.postToConsole("Use 'help start' for more info");var t=e[2];switch(e[1].toLowerCase()){case"general":case"gen":null!=G[t]?(this.action.type=K[t],this.action.name=t,this.startAction(this.action),this.updateActionAndSkillsContent()):this.postToConsole("Invalid action name specified: "+e[2]);break;case"contract":case"contracts":null!=this.contracts[t]?(this.action.type=K.Contract,this.action.name=t,this.startAction(this.action),this.updateActionAndSkillsContent()):this.postToConsole("Invalid contract name specified: "+e[2]);break;case"ops":case"op":case"operations":case"operation":null!=this.operations[t]?(this.action.type=K.Operation,this.action.name=t,this.startAction(this.action),this.updateActionAndSkillsContent()):this.postToConsole("Invalid Operation name specified: "+e[2]);break;case"blackops":case"blackop":case"black operations":case"black operation":null!=V[t]?(this.action.type=K.BlackOperation,this.action.name=t,this.startAction(this.action),this.updateActionAndSkillsContent()):this.postToConsole("Invalid BlackOp name specified: "+e[2]);break;default:this.postToConsole("Invalid action/event type specified: "+e[1]),this.postToConsole("Examples of valid action/event identifiers are: [general, contract, op, blackop]")}},J.prototype.getActionIdFromTypeAndName=function(e="",t=""){if(""===e||""===t)return null;var n=new q,i=e.toLowerCase().trim(),a=t.toLowerCase().trim();switch(i){case"contract":case"contracts":case"contr":return n.type=K.Contract,this.contracts.hasOwnProperty(t)?(n.name=t,n):null;case"operation":case"operations":case"op":case"ops":return n.type=K.Operation,this.operations.hasOwnProperty(t)?(n.name=t,n):null;case"blackoperation":case"black operation":case"black operations":case"black op":case"black ops":case"blackop":case"blackops":return n.type=K.BlackOp,V.hasOwnProperty(t)?(n.name=t,n):null;case"general":case"general action":case"gen":break;default:return null}if(i.startsWith("gen")){switch(a){case"training":n.type=K.Training,n.name="Training";break;case"recruitment":case"recruit":n.type=K.Recruitment,n.name="Recruitment";break;case"field analysis":case"fieldanalysis":n.type=K["Field Analysis"],n.name="Field Analysis";break;case"diplomacy":n.type=K.Diplomacy,n.name="Diplomacy";break;case"hyperbolic regeneration chamber":n.type=K["Hyperbolic Regeneration Chamber"],n.name="Hyperbolic Regeneration Chamber";break;default:return null}return n}},J.prototype.getTypeAndNameFromActionId=function(e){var t={};let n=Object.keys(K);for(let i=0;ithis.rank)return n.log(`Failed to start Black Op ${a.name} due to insufficient rank`),!1;if(null!=this.blackops[a.name])return n.log(`Failed to start Black Op ${a.name} because its already been completed`),!1;var r=[];for(const e in V)V.hasOwnProperty(e)&&r.push(e);r.sort(function(e,t){return V[e].reqdRank-V[t].reqdRank});let e=r.indexOf(a.name);if(-1===e)return n.log("ERROR: Invalid Black Operation name passed into bladeburner.startAction(). Note that this name is case-sensitive & whitespace-sensitive"),!1;if(e>0&&null==this.blackops[r[e-1]])return n.log(`ERROR: Cannot attempt Black Operation ${a.name} because you have not done the preceding one`),!1}try{return this.startAction(a),n.shouldLog("startAction")&&n.log("Starting bladeburner action with type "+e+" and name "+t),!0}catch(i){return this.resetAction(),n.log("ERROR: bladeburner.startAction() failed to start action of type "+e+" due to invalid name: "+t+"Note that this name is case-sensitive and whitespace-sensitive"),!1}},J.prototype.getActionTimeNetscriptFn=function(e,t,n){var i="ERROR: bladeburner.getActionTime() failed due to an invalid action specified. Type: "+e+", Name: "+t+". Note that for contracts and operations, the name of the operation is case-sensitive.",a=this.getActionIdFromTypeAndName(e,t);if(null==a)return n.log(i),-1;var r=this.getActionObject(a);if(null==r)return n.log(i),-1;switch(a.type){case K.Contract:case K.Operation:case K.BlackOp:case K.BlackOperation:return r.getActionTime(this);case K.Training:case K["Field Analysis"]:case K.FieldAnalysis:return 30;case K.Recruitment:return this.getRecruitmentTime();default:return n.log(i),-1}},J.prototype.getActionEstimatedSuccessChanceNetscriptFn=function(e,t,n){var i="ERROR: bladeburner.getActionEstimatedSuccessChance() failed due to an invalid action specified. Type: "+e+", Name: "+t+". Note that for contracts and operations, the name of the operation is case-sensitive.",a=this.getActionIdFromTypeAndName(e,t);if(null==a)return n.log(i),-1;var r=this.getActionObject(a);if(null==r)return n.log(i),-1;switch(a.type){case K.Contract:case K.Operation:case K.BlackOp:case K.BlackOperation:return r.getSuccessChance(this,{est:!0});case K.Training:case K["Field Analysis"]:case K.FieldAnalysis:return 1;case K.Recruitment:return this.getRecruitmentSuccessChance();default:return n.log(i),-1}},J.prototype.getActionCountRemainingNetscriptFn=function(e,t,n){var i="ERROR: bladeburner.getActionCountRemaining() failed due to an invalid action specified. Type: "+e+", Name: "+t+". Note that for contracts and operations, the name of the operation is case-sensitive.",a=this.getActionIdFromTypeAndName(e,t);if(null==a)return n.log(i),-1;var r=this.getActionObject(a);if(null==r)return n.log(i),-1;switch(a.type){case K.Contract:case K.Operation:return Math.floor(r.count);case K.BlackOp:case K.BlackOperation:return null!=this.blackops[t]?0:1;case K.Training:case K["Field Analysis"]:case K.FieldAnalysis:return 1/0;default:return n.log(i),-1}},J.prototype.getSkillLevelNetscriptFn=function(e,t){var n="ERROR: bladeburner.getSkillLevel() failed due to an invalid skill specified: "+e+". Note that the name of the skill is case-sensitive";return""===e?-1:F.hasOwnProperty(e)?null==this.skills[e]?0:this.skills[e]:(t.log(n),-1)},J.prototype.getSkillUpgradeCostNetscriptFn=function(e,t){var n="ERROR: bladeburner.getSkillUpgradeCostNetscriptFn() failed due to an invalid skill specified: "+e+". Note that the name of the skill is case-sensitive";if(""===e)return-1;if(!F.hasOwnProperty(e))return t.log(n),-1;var i=F[e];return null==this.skills[e]?i.calculateCost(0):i.calculateCost(this.skills[e])},J.prototype.upgradeSkillNetscriptFn=function(e,t){var n="ERROR: bladeburner.upgradeSkill() failed due to an invalid skill specified: "+e+". Note that the name of the skill is case-sensitive";if(!F.hasOwnProperty(e))return t.log(n),!1;var i=F[e],a=0;this.skills[e]&&!isNaN(this.skills[e])&&(a=this.skills[e]);var r=i.calculateCost(a);return i.maxLvl&&a>=i.maxLvl?(t.shouldLog("upgradeSkill")&&t.log(`bladeburner.upgradeSkill() failed because ${e} is already maxed`),!1):this.skillPoints=25?(Object(u.c)(t),e.shouldLog("joinBladeburnerFaction")&&e.log("Joined Bladeburners Faction"),S.routing.isOn(S.Page.Bladeburner)&&(Object(b.removeChildrenFromElement)(X.overviewDiv),this.createOverviewContent()),!0):(e.shouldLog("joinBladeburnerFaction")&&e.log("Failed to join Bladeburners Faction because you do not have the required 25 rank"),!1))},J.prototype.toJSON=function(){return Object(E.Generic_toJSON)("Bladeburner",this)},J.fromJSON=function(e){return Object(E.Generic_fromJSON)(J,e.data)},E.Reviver.constructors.Bladeburner=J}).call(this,n(73))},function(e,t,n){"use strict";n.d(t,"b",function(){return C}),n.d(t,"h",function(){return O}),n.d(t,"a",function(){return T}),n.d(t,"f",function(){return w}),n.d(t,"d",function(){return x}),n.d(t,"c",function(){return R}),n.d(t,"g",function(){return N}),n.d(t,"e",function(){return M});var i=n(79),a=n(1),r=n(10),o=n(160),s=n(138),l=n(7),c=n(47),u=n(139),p=n(83),m=n(11),d=n(18),h=n(56),g=n(130),_=n(55),y=n(9),f=n(111),b=n(64),E=n(99),v=n(4);const k=n(181);function C(e){this.name=e.filename,this.running=!1,this.serverIp=e.server,this.code=e.getCode(),this.env=new s.a(this),this.env.set("args",e.args.slice()),this.output="",this.ramUsage=0,this.scriptRef=e,this.errorMessage="",this.args=e.args.slice(),this.delay=null,this.fnWorker=null,this.checkingRam=!1,this.loadedFns={},this.disableLogs={},this.dynamicRamUsage=a.CONSTANTS.ScriptBaseRamCost,this.dynamicLoadedFns={}}C.prototype.getServer=function(){return m.b[this.serverIp]},C.prototype.getScript=function(){let e=this.getServer();for(let t=0;t{if(t instanceof Error)throw e.errorMessage=Object(l.d)(e,t.message+(t.stack&&"\nstack:\n"+t.stack.toString()||"")),e;if(Object(l.b)(t))throw e.errorMessage=t,e;throw t})}function A(e){var t,n,i=e.code;e.running=!0;try{let a=function(e,t){var n=Object(_.parse)(e,{ecmaVersion:6,allowReserved:!0,sourceType:"module"}),i=t.getServer();if(null==i)throw new Error("Failed to find underlying Server object for script");var a="",r=!1;if(k.simple(n,{ImportDeclaration:e=>{r=!0;let t=e.source.value;t.startsWith("./")&&(t=t.slice(2));let n=function(e){for(let t=0;t{n.push(e.id.name),i.push(e)}}),a="var "+t+";\n(function (namespace) {\n",i.forEach(e=>{a+=Object(g.generate)(e),a+="\n"}),n.forEach(e=>{a+="namespace."+e+" = "+e,a+="\n"}),a+="})("+t+" || ("+t+" = {}));\n"}else{let t=[];e.specifiers.forEach(e=>{t.push(e.local.name)});let n=[];k.simple(o,{FunctionDeclaration:e=>{t.includes(e.id.name)&&n.push(e)}}),n.forEach(e=>{a+=Object(g.generate)(e),a+="\n"})}}}),!r)return{code:e,lineOffset:0};var o=0;if("Program"!==n.type||null==n.body)throw new Error("Code could not be properly parsed");for(let e=n.body.length-1;e>=0;--e)"ImportDeclaration"===n.body[e].type&&(n.body.splice(e,1),++o);var s=(a.match(/\n/g)||[]).length-o;return e=Object(g.generate)(n),{code:e=a+e,lineOffset:s}}(i,e);t=a.code,n=a.lineOffset}catch(t){return Object(y.dialogBoxCreate)("Error processing Imports in "+e.name+":
"+t),e.env.stopFlag=!0,void(e.running=!1)}var a;try{a=new o.a(t,function(t,n){var i=Object(c.a)(e);for(let e in i){let a=i[e];if("function"==typeof a)if("hack"===e||"grow"===e||"weaken"===e||"sleep"===e||"prompt"===e||"run"===e||"exec"===e){let i=function(){let e=[];for(let t=0;t"+t),e.env.stopFlag=!0,void(e.running=!1)}return new Promise(function(t,n){try{!function i(){try{if(e.env.stopFlag)return n(e);a.step()?Object(h.setTimeoutRef)(i,d.Settings.CodeInstructionRunTime):t(e)}catch(t){return t=t.toString(),Object(l.b)(t)||(t=Object(l.d)(e,t)),e.errorMessage=t,n(e)}}()}catch(t){return Object(v.isString)(t)?(e.errorMessage=t,n(e)):n(t instanceof C?t:e)}})}function w(){for(var e=!1,t=O.length-1;t>=0;t--)if(0==O[t].running&&1==O[t].env.stopFlag){e=!0;var n=O[t].serverIp,a=O[t].name;m.b[n].ramUsed=0;for(let e=0;eServer Ip: "+n+"
Script name: "+i+"
Args:"+Object(b.arrayToString)(e.args)+"
"+a),e.scriptRef.log("Script crashed with runtime error")}else e.scriptRef.log("Script killed");e.running=!1,e.env.stopFlag=!0}else{if(Object(l.b)(e))return Object(y.dialogBoxCreate)("Script runtime unknown error. This is a bug please contact game developer"),void console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: "+e.toString());Object(y.dialogBoxCreate)("An unknown script died for an unknown reason. This is a bug please contact game dev"),console.log(e)}else console.log("Script returning with value: "+e[1])})}Object(h.setTimeoutRef)(w,6e3)}function x(e,t){for(var n=0;nt.maxRam-t.ramUsed)Object(y.dialogBoxCreate)("Not enough RAM to run script "+e.filename+" with args "+Object(b.arrayToString)(e.args)+". This likely occurred because you re-loaded the game and the script's RAM usage increased (either because of an update to the game or your changes to the script.)");else{t.ramUsed=Object(E.roundToTwo)(t.ramUsed+a);var r=new C(e);r.ramUsage=a,Object(i.a)(r),O.push(r)}}function N(e=1){for(var t=e*r.Engine._idleSpeed/1e3,n=0;nd.Start&&(h.currStep-=1);_()}(),!1}),Object(l.clearEventListeners)("interactive-tutorial-next").addEventListener("click",function(){return y(),!1}),_()}function _(){if(h.isRunning){var e=Object(l.clearEventListeners)("terminal-menu-link"),t=Object(l.clearEventListeners)("stats-menu-link"),n=Object(l.clearEventListeners)("active-scripts-menu-link"),a=Object(l.clearEventListeners)("hacknet-nodes-menu-link"),r=Object(l.clearEventListeners)("city-menu-link"),o=Object(l.clearEventListeners)("tutorial-menu-link");e.removeAttribute("class"),t.removeAttribute("class"),n.removeAttribute("class"),a.removeAttribute("class"),r.removeAttribute("class"),o.removeAttribute("class");var s=document.getElementById("interactive-tutorial-next");switch(h.currStep){case d.Start:i.Engine.loadTerminalContent(),b("Welcome to Bitburner, a cyberpunk-themed incremental RPG! The game takes place in a dark, dystopian future...The year is 2077...

This tutorial will show you the basics of the game. You may skip the tutorial at any time."),s.style.display="inline-block";break;case d.GoToCharacterPage:i.Engine.loadTerminalContent(),b("Let's start by heading to the Stats page. Click the 'Stats' tab on the main navigation menu (left-hand side of the screen)"),s.style.display="none",t.setAttribute("class","flashing-button"),t.addEventListener("click",function(){return i.Engine.loadCharacterContent(),y(),!1});break;case d.CharacterPage:i.Engine.loadCharacterContent(),b("The Stats page shows a lot of important information about your progress, such as your skills, money, and bonuses/multipliers. "),s.style.display="inline-block";break;case d.CharacterGoToTerminalPage:i.Engine.loadCharacterContent(),b("Let's head to your computer's terminal by clicking the 'Terminal' tab on the main navigation menu."),s.style.display="none",e.setAttribute("class","flashing-button"),e.addEventListener("click",function(){return i.Engine.loadTerminalContent(),y(),!1});break;case d.TerminalIntro:i.Engine.loadTerminalContent(),b("The Terminal is used to interface with your home computer as well as all of the other machines around the world."),s.style.display="inline-block";break;case d.TerminalHelp:i.Engine.loadTerminalContent(),b("Let's try it out. Start by entering the 'help' command into the Terminal (Don't forget to press Enter after typing the command)"),s.style.display="none";break;case d.TerminalLs:i.Engine.loadTerminalContent(),b("The 'help' command displays a list of all available Terminal commands, how to use them, and a description of what they do.

Let's try another command. Enter the 'ls' command"),s.style.display="none";break;case d.TerminalScan:i.Engine.loadTerminalContent(),b("'ls' is a basic command that shows all of the contents (programs/scripts) on the computer. Right now, it shows that you have a program called 'NUKE.exe' on your computer. We'll get to what this does later.

Using your home computer's terminal, you can connect to other machines throughout the world. Let's do that now by first entering the 'scan' command."),s.style.display="none";break;case d.TerminalScanAnalyze1:i.Engine.loadTerminalContent(),b("The 'scan' command shows all available network connections. In other words, it displays a list of all servers that can be connected to from your current machine. A server is identified by either its IP or its hostname.

That's great and all, but there's so many servers. Which one should you go to? The 'scan-analyze' command gives some more detailed information about servers on the network. Try it now"),s.style.display="none";break;case d.TerminalScanAnalyze2:i.Engine.loadTerminalContent(),b("You just ran 'scan-analyze' with a depth of one. This command shows more detailed information about each server that you can connect to (servers that are a distance of one node away).

It is also possible to run 'scan-analyze' with a higher depth. Let's try a depth of two with the following command: 'scan-analyze 2'."),s.style.display="none";break;case d.TerminalConnect:i.Engine.loadTerminalContent(),b("Now you can see information about all servers that are up to two nodes away, as well as figure out how to navigate to those servers through the network. You can only connect to a server that is one node away. To connect to a machine, use the 'connect [ip/hostname]' command. You can type in the ip or the hostname, but dont use both.

From the results of the 'scan-analyze' command, we can see that the 'foodnstuff' server is only one node away. Let's connect so it now using: 'connect foodnstuff'"),s.style.display="none";break;case d.TerminalAnalyze:i.Engine.loadTerminalContent(),b("You are now connected to another machine! What can you do now? You can hack it!

In the year 2077, currency has become digital and decentralized. People and corporations store their money on servers and computers. Using your hacking abilities, you can hack servers to steal money and gain experience.

Before you try to hack a server, you should run diagnostics using the 'analyze' command"),s.style.display="none";break;case d.TerminalNuke:i.Engine.loadTerminalContent(),b("When the 'analyze' command finishes running it will show useful information about hacking the server.

For this server, the required hacking skill is only 1, which means you can hack it right now. However, in order to hack a server you must first gain root access. The 'NUKE.exe' program that we saw earlier on your home computer is a virus that will grant you root access to a machine if there are enough open ports.

The 'analyze' results shows that there do not need to be any open ports on this machine for the NUKE virus to work, so go ahead and run the virus using the 'run NUKE.exe' command."),s.style.display="none";break;case d.TerminalManualHack:i.Engine.loadTerminalContent(),b("You now have root access! You can hack the server using the 'hack' command. Try doing that now."),s.style.display="none";break;case d.TerminalHackingMechanics:i.Engine.loadTerminalContent(),b("You are now attempting to hack the server. Note that performing a hack takes time and only has a certain percentage chance of success. This time and success chance is determined by a variety of factors, including your hacking skill and the server's security level.

If your attempt to hack the server is successful, you will steal a certain percentage of the server's total money. This percentage is affected by your hacking skill and the server's security level.

The amount of money on a server is not limitless. So, if you constantly hack a server and deplete its money, then you will encounter diminishing returns in your hacking."),s.style.display="inline-block";break;case d.TerminalCreateScript:i.Engine.loadTerminalContent(),b("Hacking is the core mechanic of the game and is necessary for progressing. However, you don't want to be hacking manually the entire time. You can automate your hacking by writing scripts!

To create a new script or edit an existing one, you can use the 'nano' command. Scripts must end with the '.script' extension. Let's make a script now by entering 'nano foodnstuff.script' after the hack command finishes running (Sidenote: Pressing ctrl + c will end a command like hack early)"),s.style.display="none";break;case d.TerminalTypeScript:i.Engine.loadScriptEditorContent("foodnstuff.script",""),b("This is the script editor. You can use it to program your scripts. Scripts are written in the Netscript language, a programming language created for this game. There are details about the Netscript language in the documentation, which can be accessed in the 'Tutorial' tab on the main navigation menu. I highly suggest you check it out after this tutorial. For now, just copy and paste the following code into the script editor:

while(true) {
  hack('foodnstuff');
}

For anyone with basic programming experience, this code should be straightforward. This script will continuously hack the 'foodnstuff' server.

To save and close the script editor, press the button in the bottom left, or press ctrl + b."),s.style.display="none";break;case d.TerminalFree:i.Engine.loadTerminalContent(),b("Now we'll run the script. Scripts require a certain amount of RAM to run, and can be run on any machine which you have root access to. Different servers have different amounts of RAM. You can also purchase more RAM for your home server.

To check how much RAM is available on this machine, enter the 'free' command."),s.style.display="none";break;case d.TerminalRunScript:i.Engine.loadTerminalContent(),b("We have 16GB of free RAM on this machine, which is enough to run our script. Let's run our script using 'run foodnstuff.script'."),s.style.display="none";break;case d.TerminalGoToActiveScriptsPage:i.Engine.loadTerminalContent(),b("Your script is now running! The script might take a few seconds to 'fully start up'. Your scripts will continuously run in the background and will automatically stop if the code ever completes (the 'foodnstuff.script' will never complete because it runs an infinite loop).

These scripts can passively earn you income and hacking experience. Your scripts will also earn money and experience while you are offline, although at a much slower rate.

Let's check out some statistics for our running scripts by clicking the 'Active Scripts' link in the main navigation menu."),s.style.display="none",n.setAttribute("class","flashing-button"),n.addEventListener("click",function(){return i.Engine.loadActiveScriptsContent(),y(),!1});break;case d.ActiveScriptsPage:i.Engine.loadActiveScriptsContent(),b("This page displays stats/information about all of your scripts that are running across every existing server. You can use this to gauge how well your scripts are doing. Let's go back to the Terminal now using the 'Terminal'link."),s.style.display="none",e.setAttribute("class","flashing-button"),e.addEventListener("click",function(){return i.Engine.loadTerminalContent(),y(),!1});break;case d.ActiveScriptsToTerminal:i.Engine.loadTerminalContent(),b("One last thing about scripts, each active script contains logs that detail what it's doing. We can check these logs using the 'tail' command. Do that now for the script we just ran by typing 'tail foodnstuff.script'"),s.style.display="none";break;case d.TerminalTailScript:i.Engine.loadTerminalContent(),b("The log for this script won't show much right now (it might show nothing at all) because it just started running...but check back again in a few minutes!

This pretty much covers the basics of hacking. To learn more about writing scripts using the Netscript language, select the 'Tutorial' link in the main navigation menu to look at the documentation. If you are an experienced JavaScript developer, I would highly suggest you check out the section on NetscriptJS/Netscript 2.0.

For now, let's move on to something else!"),s.style.display="inline-block";break;case d.GoToHacknetNodesPage:i.Engine.loadTerminalContent(),b("Hacking is not the only way to earn money. One other way to passively earn money is by purchasing and upgrading Hacknet Nodes. Let's go to the 'Hacknet Nodes' page through the main navigation menu now."),s.style.display="none",a.setAttribute("class","flashing-button"),a.addEventListener("click",function(){return i.Engine.loadHacknetNodesContent(),y(),!1});break;case d.HacknetNodesIntroduction:i.Engine.loadHacknetNodesContent(),b("From this page you can purchase new Hacknet Nodes and upgrade your existing ones. Let's purchase a new one now."),s.style.display="none";break;case d.HacknetNodesGoToWorldPage:i.Engine.loadHacknetNodesContent(),b("You just purchased a Hacknet Node! This Hacknet Node will passively earn you money over time, both online and offline. When you get enough money, you can upgrade your newly-purchased Hacknet Node below.

Let's go to the 'City' page through the main navigation menu."),s.style.display="none",r.setAttribute("class","flashing-button"),r.addEventListener("click",function(){return i.Engine.loadWorldContent(),y(),!1});break;case d.WorldDescription:i.Engine.loadWorldContent(),b("This page lists all of the different locations you can currently travel to. Each location has something that you can do. There's a lot of content out in the world, make sure you explore and discover!

Lastly, click on the 'Tutorial' link in the main navigation menu."),s.style.display="none",o.setAttribute("class","flashing-button"),o.addEventListener("click",function(){return i.Engine.loadTutorialContent(),y(),!1});break;case d.TutorialPageInfo:i.Engine.loadTutorialContent(),b("This page contains a lot of different documentation about the game's content and mechanics. I know it's a lot, but I highly suggest you read (or at least skim) through this before you start playing. That's the end of the tutorial. Hope you enjoy the game!"),s.style.display="inline-block",s.innerHTML="Finish Tutorial";break;case d.End:f();break;default:throw new Error("Invalid tutorial step")}!0===h.stepIsDone[h.currStep]&&(s.style.display="inline-block")}else console.log("Interactive Tutorial not running")}function y(){h.currStep===d.GoToCharacterPage&&document.getElementById("stats-menu-link").removeAttribute("class"),h.currStep===d.CharacterGoToTerminalPage&&document.getElementById("terminal-menu-link").removeAttribute("class"),h.currStep===d.TerminalGoToActiveScriptsPage&&document.getElementById("active-scripts-menu-link").removeAttribute("class"),h.currStep===d.ActiveScriptsPage&&document.getElementById("terminal-menu-link").removeAttribute("class"),h.currStep===d.GoToHacknetNodesPage&&document.getElementById("hacknet-nodes-menu-link").removeAttribute("class"),h.currStep===d.HacknetNodesGoToWorldPage&&document.getElementById("city-menu-link").removeAttribute("class"),h.currStep===d.WorldDescription&&document.getElementById("tutorial-menu-link").removeAttribute("class"),h.stepIsDone[h.currStep]=!0,h.currStep
Getting Started GuideDocumentation

The Beginner's Guide to Hacking was added to your home computer! It contains some tips/pointers for starting out with the game. To read it, go to Terminal and enter

cat hackers-starting-handbook.lit"}),n=Object(c.createElement)("a",{class:"a-link-button",float:"right",padding:"6px",innerText:"Got it!",clickListener:()=>{Object(p.removeElementById)(e)}});Object(u.createPopup)(e,[t,n]),a.a.getHomeComputer().messages.push("hackers-starting-handbook.lit")}function b(e){var t=document.getElementById("interactive-tutorial-text");if(null==t)throw new Error("Could not find text box");t.innerHTML=e,t.parentElement.scrollTop=0}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SoftwareCompanyPositions=["Software Engineering Intern","Junior Software Engineer","Senior Software Engineer","Lead Software Developer","Head of Software","Head of Engineering","Vice President of Technology","Chief Technology Officer"],t.ITCompanyPositions=["IT Intern","IT Analyst","IT Manager","Systems Administrator"],t.SecurityEngineerCompanyPositions=["Security Engineer"],t.NetworkEngineerCompanyPositions=["Network Engineer","Network Administrator"],t.BusinessCompanyPositions=["Business Intern","Business Analyst","Business Manager","Operations Manager","Chief Financial Officer","Chief Executive Officer"],t.SecurityCompanyPositions=["Police Officer","Police Chief","Security Guard","Security Officer","Security Supervisor","Head of Security"],t.AgentCompanyPositions=["Field Agent","Secret Agent","Special Operative"],t.MiscCompanyPositions=["Waiter","Employee"],t.SoftwareConsultantCompanyPositions=["Software Consultant","Senior Software Consultant"],t.BusinessConsultantCompanyPositions=["Business Consultant","Senior Business Consultant"],t.PartTimeCompanyPositions=["Part-time Waiter","Part-time Employee"]},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(9);t.exceptionAlert=function(e){i.dialogBoxCreate("Caught an exception: "+e+"

Filename: "+(e.fileName||"UNKNOWN FILE NAME")+"

Line Number: "+(e.lineNumber||"UNKNOWN LINE NUMBER")+"

This is a bug, please report to game developer with this message as well as details about how to reproduce the bug.

If you want to be safe, I suggest refreshing the game WITHOUT saving so that your safe doesn't get corrupted",!1)}},function(e,t,n){"use strict";n.d(t,"b",function(){return o}),n.d(t,"a",function(){return s});var i=n(0),a=n(58),r=n(7);function o(e,t){return`gang.${e}() failed with exception: `+t}function s(e,t){const n=`gang.${t}() failed because you do not currently have a Gang`;if(!(i.a.gang instanceof a.b))throw Object(r.d)(e,n)}},,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e.Ace="ace",e.Emacs="emacs",e.Vim="vim"}(t.AceKeybindingSetting||(t.AceKeybindingSetting={})),function(e){e.Default="default",e.Emacs="emacs",e.Sublime="sublime",e.Vim="vim"}(t.CodeMirrorKeybindingSetting||(t.CodeMirrorKeybindingSetting={})),function(e){e.Monokai="monokai",e.Day_3024="3024-day",e.Night_3024="3024-night",e.abcdef="abcdef",e.Ambiance_mobile="ambiance-mobile",e.Ambiance="ambiance",e.Base16_dark="base16-dark",e.Base16_light="base16-light",e.Bespin="bespin",e.Blackboard="blackboard",e.Cobalt="cobalt",e.Colorforth="colorforth",e.Darcula="darcula",e.Dracula="dracula",e.Duotone_dark="duotone-dark",e.Duotone_light="duotone-light",e.Eclipse="eclipse",e.Elegant="elegant",e.Erlang_dark="erlang-dark",e.Gruvbox_dark="gruvbox-dark",e.Hopscotch="hopscotch",e.Icecoder="icecoder",e.Idea="idea",e.Isotope="isotope",e.Lesser_dark="lesser-dark",e.Liquibyte="liquibyte",e.Lucario="lucario",e.Material="material",e.Mbo="mbo",e.Mdn_like="mdn-like",e.Midnight="midnight",e.Neat="neat",e.Neo="neo",e.Night="night",e.Oceanic_next="oceanic-next",e.Panda_syntax="panda-syntax",e.Paraiso_dark="paraiso-dark",e.Paraiso_light="paraiso-light",e.Pastel_on_dark="pastel-on-dark",e.Railscasts="railscasts",e.Rubyblue="rubyblue",e.Seti="seti",e.Shadowfox="shadowfox",e.Solarized="solarized",e.ssms="ssms",e.The_matrix="the-matrix",e.Tomorrow_night_bright="tomorrow-night-bright",e.Tomorrow_night_eighties="tomorrow-night-eighties",e.Ttcn="ttcn",e.Twilight="twilight",e.Vibrant_ink="vibrant-ink",e.xq_dark="xq-dark",e.xq_light="xq-light",e.Yeti="yeti",e.Zenburn="zenburn"}(t.CodeMirrorThemeSetting||(t.CodeMirrorThemeSetting={})),function(e){e.Ace="Ace",e.CodeMirror="CodeMirror"}(t.EditorSetting||(t.EditorSetting={})),function(e){e[e.Cost=0]="Cost",e[e.Default=1]="Default",e[e.Reputation=2]="Reputation"}(t.PurchaseAugmentationsOrderSetting||(t.PurchaseAugmentationsOrderSetting={})),function(e){e[e.Alphabetically=0]="Alphabetically",e[e.AcquirementTime=1]="AcquirementTime"}(t.OwnedAugmentationsOrderSetting||(t.OwnedAugmentationsOrderSetting={}))},function(e,t,n){"use strict";n.d(t,"b",function(){return f}),n.d(t,"c",function(){return _}),n.d(t,"f",function(){return g}),n.d(t,"e",function(){return b}),n.d(t,"d",function(){return v}),n.d(t,"a",function(){return d});n(17);var i=n(13),a=n(6),r=n(28),o=n(65),s=n(0),l=n(68),c=n(11),u=n(18),p=n(9),m=n(14);function d(e="",t=""){this.filename=e,this.msg=t,this.recvd=!1}function h(e,t=!1){e.recvd=!0,!t&&u.Settings.SuppressMessages||g(e),function(e,t){var n=Object(c.c)(t);if(null==n)return void console.log("WARNING: Did not locate "+t);for(var i=0;i

This message was saved as "+e.filename+" onto your home computer.";Object(p.dialogBoxCreate)(t)}function _(){var e=f[E.Jumper0],t=f[E.Jumper1],n=f[E.Jumper2],c=f[E.Jumper3],u=f[E.Jumper4],m=f[E.CyberSecTest],d=f[E.NiteSecTest],g=f[E.BitRunnersTest],_=f[E.RedPill],y=!1;i.Augmentations[a.AugmentationNames.TheRedPill].owned&&(y=!0),_&&y&&0===s.a.sourceFiles.length&&!l.b&&!o.c?p.dialogBoxOpened||h(_,!0):_&&y?l.b||o.c||p.dialogBoxOpened||h(_):e&&!e.recvd&&s.a.hacking_skill>=25?(h(e),s.a.getHomeComputer().programs.push(r.Programs.Flight.name)):t&&!t.recvd&&s.a.hacking_skill>=40?h(t):m&&!m.recvd&&s.a.hacking_skill>=50?h(m):n&&!n.recvd&&s.a.hacking_skill>=175?h(n):d&&!d.recvd&&s.a.hacking_skill>=200?h(d):c&&!c.recvd&&s.a.hacking_skill>=350?h(c):u&&!u.recvd&&s.a.hacking_skill>=490?h(u):g&&!g.recvd&&s.a.hacking_skill>=500&&h(g)}function y(e){f[e.filename]=e}d.prototype.toJSON=function(){return Object(m.Generic_toJSON)("Message",this)},d.fromJSON=function(e){return Object(m.Generic_fromJSON)(d,e.data)},m.Reviver.constructors.Message=d;let f={};function b(e){f=JSON.parse(e,m.Reviver)}let E={Jumper0:"j0.msg",Jumper1:"j1.msg",Jumper2:"j2.msg",Jumper3:"j3.msg",Jumper4:"j4.msg",CyberSecTest:"csec-test.msg",NiteSecTest:"nitesec-test.msg",BitRunnersTest:"19dfj3l1nd.msg",RedPill:"icarus.msg"};function v(){f={},y(new d(E.Jumper0,"I know you can sense it. I know you're searching for it. It's why you spend night after night at your computer.

It's real, I've seen it. And I can help you find it. But not right now. You're not ready yet.

Use this program to track your progress

The fl1ght.exe program was added to your home computer

-jump3R")),y(new d(E.Jumper1,"Soon you will be contacted by a hacking group known as CyberSec. They can help you with your search.

You should join them, garner their favor, and exploit them for their Augmentations. But do not trust them. They are not what they seem. No one is.

-jump3R")),y(new d(E.Jumper2,"Do not try to save the world. There is no world to save. If you want to find the truth, worry only about yourself. Ethics and morals will get you killed.

Watch out for a hacking group known as NiteSec.

-jump3R")),y(new d(E.Jumper3,"You must learn to walk before you can run. And you must run before you can fly. Look for the black hand.

I.I.I.I

-jump3R")),y(new d(E.Jumper4,"To find what you are searching for, you must understand the bits. The bits are all around us. The runners will help you.

-jump3R")),y(new d(E.CyberSecTest,"We've been watching you. Your skills are very impressive. But you're wasting your talents. If you join us, you can put your skills to good use and change the world for the better. If you join us, we can unlock your full potential.

But first, you must pass our test. Find and hack our server using the Terminal.

-CyberSec")),y(new d(E.NiteSecTest,"People say that the corrupted governments and corporations rule the world. Yes, maybe they do. But do you know who everyone really fears? People like us. Because they can't hide from us. Because they can't fight shadows and ideas with bullets.

Join us, and people will fear you, too.

Find and hack our hidden server using the Terminal. Then, we will contact you again.

-NiteSec")),y(new d(E.BitRunnersTest,"We know what you are doing. We know what drives you. We know what you are looking for.

We can help you find the answers.

run4theh111z")),y(new d(E.RedPill,"@)(#V%*N)@(#*)*C)@#%*)*V)@#(*%V@)(#VN%*)@#(*%
)@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)
@_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB
DFLSMFVMV)#@($*)@#*$MV)@#(*$V)M#(*$)M@(#*VM$)"))}},function(e,t,n){"use strict";var i=this&&this.__awaiter||function(e,t,n,i){return new(n||(n=Promise))(function(a,r){function o(e){try{l(i.next(e))}catch(e){r(e)}}function s(e){try{l(i.throw(e))}catch(e){r(e)}}function l(e){e.done?a(e.value):new n(function(t){t(e.value)}).then(o,s)}l((i=i.apply(e,t||[])).next())})};Object.defineProperty(t,"__esModule",{value:!0});const a=n(257),r=n(14),o=n(29),s=n(2),l=n(37),c=n(23);class u{constructor(e,t,n,i,a,r){this.name=e,this.desc=t,this.generate=n,this.solver=i,this.difficulty=a,this.numTries=r}}t.CodingContractType=u,t.CodingContractTypes={};for(const e of a.codingContractTypesMetadata)t.CodingContractTypes[e.name]=new u(e.name,e.desc,e.gen,e.solver,e.difficulty,e.numTries);var p;console.info(`${Object.keys(t.CodingContractTypes).length} Coding Contract Types loaded`),function(e){e[e.FactionReputation=0]="FactionReputation",e[e.FactionReputationAll=1]="FactionReputationAll",e[e.CompanyReputation=2]="CompanyReputation",e[e.Money=3]="Money"}(t.CodingContractRewardType||(t.CodingContractRewardType={})),function(e){e[e.Success=0]="Success",e[e.Failure=1]="Failure",e[e.Cancelled=2]="Cancelled"}(p=t.CodingContractResult||(t.CodingContractResult={}));class m{constructor(e="",n="Find Largest Prime Factor",i=null){if(this.tries=0,this.fn=e,this.fn.endsWith(".cct")||(this.fn+=".cct"),null==t.CodingContractTypes[n])throw new Error(`Error: invalid contract type: ${n} please contact developer`);this.type=n,this.data=t.CodingContractTypes[n].generate(),this.reward=i}static fromJSON(e){return r.Generic_fromJSON(m,e.data)}getData(){return this.data}getDescription(){return t.CodingContractTypes[this.type].desc(this.data)}getDifficulty(){return t.CodingContractTypes[this.type].difficulty}getMaxNumTries(){return t.CodingContractTypes[this.type].numTries}getType(){return t.CodingContractTypes[this.type].name}isSolution(e){return t.CodingContractTypes[this.type].solver(this.data,e)}prompt(){return i(this,void 0,void 0,function*(){return new Promise((e,n)=>{const i=t.CodingContractTypes[this.type],a=`coding-contract-prompt-popup-${this.fn}`,r=s.createElement("p",{innerHTML:["You are attempting to solve a Coding Contract. You have",`${this.getMaxNumTries()-this.tries} tries remaining,`,"after which the contract will self-destruct.

",`${i.desc(this.data).replace(/\n/g,"
")}`].join(" ")});let u,m,d;u=s.createElement("input",{onkeydown:e=>{e.keyCode===o.KEY.ENTER&&""!==u.value?(e.preventDefault(),m.click()):e.keyCode===o.KEY.ESC&&(e.preventDefault(),d.click())},placeholder:"Enter Solution here"}),m=s.createElement("a",{class:"a-link-button",clickListener:()=>{const t=u.value;this.isSolution(t)?e(p.Success):e(p.Failure),c.removeElementById(a)},innerText:"Solve"}),d=s.createElement("a",{class:"a-link-button",clickListener:()=>{e(p.Cancelled),c.removeElementById(a)},innerText:"Cancel"});const h=s.createElement("br");l.createPopup(a,[r,h,h,u,m,d]),u.focus()})})}toJSON(){return r.Generic_toJSON("CodingContract",this)}}t.CodingContract=m,r.Reviver.constructors.CodingContract=m},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(155),a=n(1);t.Crimes={Shoplift:new i.Crime("Shoplift",a.CONSTANTS.CrimeShoplift,2e3,15e3,.05,.1,{dexterity_success_weight:1,agility_success_weight:1,dexterity_exp:2,agility_exp:2}),RobStore:new i.Crime("Rob Store",a.CONSTANTS.CrimeRobStore,6e4,4e5,.2,.5,{hacking_exp:30,dexterity_exp:45,agility_exp:45,hacking_success_weight:.5,dexterity_success_weight:2,agility_success_weight:1,intelligence_exp:.25*a.CONSTANTS.IntelligenceCrimeBaseExpGain}),Mug:new i.Crime("Mug",a.CONSTANTS.CrimeMug,4e3,36e3,.2,.25,{strength_exp:3,defense_exp:3,dexterity_exp:3,agility_exp:3,strength_success_weight:1.5,defense_success_weight:.5,dexterity_success_weight:1.5,agility_success_weight:.5}),Larceny:new i.Crime("Larceny",a.CONSTANTS.CrimeLarceny,9e4,8e5,1/3,1.5,{hacking_exp:45,dexterity_exp:60,agility_exp:60,hacking_success_weight:.5,dexterity_success_weight:1,agility_success_weight:1,intelligence_exp:.5*a.CONSTANTS.IntelligenceCrimeBaseExpGain}),DealDrugs:new i.Crime("Deal Drugs",a.CONSTANTS.CrimeDrugs,1e4,12e4,1,.5,{dexterity_exp:5,agility_exp:5,charisma_exp:10,charisma_success_weight:3,dexterity_success_weight:2,agility_success_weight:1}),BondForgery:new i.Crime("Bond Forgery",a.CONSTANTS.CrimeBondForgery,3e5,45e5,.5,.1,{hacking_exp:100,dexterity_exp:150,charisma_exp:15,hacking_success_weight:.05,dexterity_success_weight:1.25,intelligence_exp:2*a.CONSTANTS.IntelligenceCrimeBaseExpGain}),TraffickArms:new i.Crime("Traffick Arms",a.CONSTANTS.CrimeTraffickArms,4e4,6e5,2,1,{strength_exp:20,defense_exp:20,dexterity_exp:20,agility_exp:20,charisma_exp:40,charisma_success_weight:1,strength_success_weight:1,defense_success_weight:1,dexterity_success_weight:1,agility_success_weight:1}),Homicide:new i.Crime("Homicide",a.CONSTANTS.CrimeHomicide,3e3,45e3,1,3,{strength_exp:2,defense_exp:2,dexterity_exp:2,agility_exp:2,strength_success_weight:2,defense_success_weight:2,dexterity_success_weight:.5,agility_success_weight:.5,kills:1}),GrandTheftAuto:new i.Crime("Grand Theft Auto",a.CONSTANTS.CrimeGrandTheftAuto,8e4,16e5,8,5,{strength_exp:20,defense_exp:20,dexterity_exp:20,agility_exp:80,charisma_exp:40,hacking_success_weight:1,strength_success_weight:1,dexterity_success_weight:4,agility_success_weight:2,charisma_success_weight:2,intelligence_exp:a.CONSTANTS.IntelligenceCrimeBaseExpGain}),Kidnap:new i.Crime("Kidnap",a.CONSTANTS.CrimeKidnap,12e4,36e5,5,6,{strength_exp:80,defense_exp:80,dexterity_exp:80,agility_exp:80,charisma_exp:80,charisma_success_weight:1,strength_success_weight:1,dexterity_success_weight:1,agility_success_weight:1,intelligence_exp:2*a.CONSTANTS.IntelligenceCrimeBaseExpGain}),Assassination:new i.Crime("Assassination",a.CONSTANTS.CrimeAssassination,3e5,12e6,8,10,{strength_exp:300,defense_exp:300,dexterity_exp:300,agility_exp:300,strength_success_weight:1,dexterity_success_weight:2,agility_success_weight:1,intelligence_exp:5*a.CONSTANTS.IntelligenceCrimeBaseExpGain,kills:1}),Heist:new i.Crime("Heist",a.CONSTANTS.CrimeHeist,6e5,12e7,18,15,{hacking_exp:450,strength_exp:450,defense_exp:450,dexterity_exp:450,agility_exp:450,charisma_exp:450,hacking_success_weight:1,strength_success_weight:1,defense_success_weight:1,dexterity_success_weight:1,agility_success_weight:1,charisma_success_weight:1,intelligence_exp:10*a.CONSTANTS.IntelligenceCrimeBaseExpGain})}},function(e,t,n){"use strict";(function(e){n.d(t,"a",function(){return pe}),n.d(t,"g",function(){return ue}),n.d(t,"d",function(){return oe}),n.d(t,"f",function(){return re}),n.d(t,"h",function(){return le}),n.d(t,"e",function(){return ee}),n.d(t,"b",function(){return ne}),n.d(t,"c",function(){return ie});var i=n(79),a=n(17),r=n(13),o=n(66),s=n(6),l=n(20),c=n(113),u=n(34),p=n(74),m=n(19),d=n(109),h=n(33),g=n(1),_=n(82),y=n(53),f=n(58),b=n(63),E=n(15),v=n(51),k=n(78),C=n(5),O=n(44),T=n(65),S=n(0),M=n(28),P=n(32),A=n(11),w=n(57),x=(n(18),n(36)),R=(n(31),n(24)),N=n(80),I=n(75),L=n(61),D=n(41),B=n(35),j=n(7),W=n(83),F=n(12),U=n(3),H=n(8),G=n(56),K=n(161),q=(n(9),n(137),n(64)),$=n(71),Y=n(4),z=n(48),V=n(2),J=n(37),X=n(23),Q=n(182).sprintf,Z=n(182).vsprintf,ee=!1,te=!1,ne=!1,ie=!1,ae=!1,re=!1,oe=!1,se=1,le=1,ce={ALL:!0,scan:!0,hack:!0,sleep:!0,disableLog:!0,enableLog:!0,grow:!0,weaken:!0,nuke:!0,brutessh:!0,ftpcrack:!0,relaysmtp:!0,httpworm:!0,sqlinject:!0,run:!0,exec:!0,spawn:!0,kill:!0,killall:!0,scp:!0,getHackingLevel:!0,getServerMoneyAvailable:!0,getServerSecurityLevel:!0,getServerBaseSecurityLevel:!0,getServerMinSecurityLevel:!0,getServerRequiredHackingLevel:!0,getServerMaxMoney:!0,getServerGrowth:!0,getServerNumPortsRequired:!0,getServerRam:!0,buyStock:!0,sellStock:!0,shortStock:!0,sellShort:!0,purchase4SMarketData:!0,purchase4SMarketDataTixApi:!0,purchaseServer:!0,deleteServer:!0,universityCourse:!0,gymWorkout:!0,travelToCity:!0,purchaseTor:!0,purchaseProgram:!0,stopAction:!0,upgradeHomeRam:!0,workForCompany:!0,applyToCompany:!0,joinFaction:!0,workForFaction:!0,donateToFaction:!0,createProgram:!0,commitCrime:!0,startAction:!0,upgradeSkill:!0,setTeamSize:!0,joinBladeburnerFaction:!0,recruitMember:!0,setMemberTask:!0,purchaseEquipment:!0,setTerritoryWarfare:!0};function ue(){for(var e=0;e1.01*t.ramUsage)throw Object(j.d)(t,"Dynamic RAM usage calculated to be greater than initial RAM usage on fn: "+e+". This is probably because you somehow circumvented the static RAM calculation.

Please don't do that :(

Dynamic RAM Usage: "+t.dynamicRamUsage+"
Static RAM Usage: "+t.ramUsage)},ee=function(e,n){return t.loadedFns[e]?0:(t.loadedFns[e]=!0,n)},ie=function(e,n=""){var i=Object(A.e)(e);if(null==i)throw Object(j.d)(t,`Invalid IP or hostname passed into ${n}() function`);return i},oe=function(e){if(isNaN(e))throw Object(j.d)(t,"Invalid index specified for Hacknet Node: "+e);if(e<0||e>=S.a.hacknetNodes.length)throw Object(j.d)(t,"Index specified for Hacknet Node is out-of-bounds: "+e);return S.a.hacknetNodes[e]},ue=function(e,t){return ie(t,"getCodingContract").getContract(e)};return{hacknet:{numNodes:function(){return S.a.hacknetNodes.length},purchaseNode:function(){return Object(k.e)()},getPurchaseNodeCost:function(){return Object(k.c)()},getNodeStats:function(e){var t=oe(e);return{name:t.name,level:t.level,ram:t.ram,cores:t.cores,production:t.moneyGainRatePerSecond,timeOnline:t.onlineTimeSeconds,totalProduction:t.totalMoneyGenerated}},upgradeLevel:function(e,t){return oe(e).purchaseLevelUpgrade(t)},upgradeRam:function(e,t){return oe(e).purchaseRamUpgrade(t)},upgradeCore:function(e,t){return oe(e).purchaseCoreUpgrade(t)},getLevelUpgradeCost:function(e,t){return oe(e).calculateLevelUpgradeCost(t)},getRamUpgradeCost:function(e,t){return oe(e).calculateRamUpgradeCost(t)},getCoreUpgradeCost:function(e,t){return oe(e).calculateCoreUpgradeCost(t)}},sprintf:Q,vsprintf:Z,scan:function(e=t.serverIp,i=!0){if(t.checkingRam)return ee("scan",g.CONSTANTS.ScriptScanRamCost);n("scan",g.CONSTANTS.ScriptScanRamCost);var a=Object(A.e)(e);if(null==a)throw Object(j.d)(t,"Invalid IP or hostname passed into scan() command");for(var r=[],o=0;oS.a.hacking_skill)throw t.scriptRef.log("Cannot hack this server ("+a.hostname+") because user's hacking skill is not high enough"),Object(j.d)(t,"Cannot hack this server ("+a.hostname+") because user's hacking skill is not high enough");return null==t.disableLogs.ALL&&null==t.disableLogs.hack&&t.scriptRef.log("Attempting to hack "+e+" in "+r.toFixed(3)+" seconds (t="+i+")"),Object(j.e)(1e3*r,t).then(function(){if(t.env.stopFlag)return Promise.reject(t);var e=Object(y.b)(a),n=Math.random(),r=Object(y.c)(a)*i,o=r/4;if(na.moneyAvailable&&(s=a.moneyAvailable),a.moneyAvailable-=s,a.moneyAvailable<0&&(a.moneyAvailable=0),S.a.gainMoney(s),t.scriptRef.onlineMoneyMade+=s,S.a.scriptProdSinceLastAug+=s,S.a.recordMoneySource(s,"hacking"),t.scriptRef.recordHack(a.ip,s,i),S.a.gainHackingExp(r),t.scriptRef.onlineExpGained+=r,null==t.disableLogs.ALL&&null==t.disableLogs.hack&&t.scriptRef.log("Script SUCCESSFULLY hacked "+a.hostname+" for $"+Object(Y.formatNumber)(s,2)+" and "+Object(Y.formatNumber)(r,4)+" exp (t="+i+")"),a.fortify(g.CONSTANTS.ServerFortifyAmount*Math.min(i,n)),Promise.resolve(s)}return S.a.gainHackingExp(o),t.scriptRef.onlineExpGained+=o,null==t.disableLogs.ALL&&null==t.disableLogs.hack&&t.scriptRef.log("Script FAILED to hack "+a.hostname+". Gained "+Object(Y.formatNumber)(o,4)+" exp (t="+i+")"),Promise.resolve(0)})},hackAnalyzeThreads:function(e,i){if(t.checkingRam)return ee("hackAnalyzeThreads",g.CONSTANTS.ScriptHackAnalyzeRamCost);n("hackAnalyzeThreads",g.CONSTANTS.ScriptHackAnalyzeRamCost);const a=ie(e,"hackAnalyzeThreads");if(isNaN(i))throw Object(j.d)(t,`Invalid growth argument passed into hackAnalyzeThreads: ${i}. Must be numeric`);if(i<0||i>a.moneyAvailable)return-1;const r=Object(y.e)(a);return i/Math.floor(a.moneyAvailable*r)},hackAnalyzePercent:function(e){if(t.checkingRam)return ee("hackAnalyzePercent",g.CONSTANTS.ScriptHackAnalyzeRamCost);n("hackAnalyzePercent",g.CONSTANTS.ScriptHackAnalyzeRamCost);const i=ie(e,"hackAnalyzePercent");return 100*Object(y.e)(i)},hackChance:function(e){if(t.checkingRam)return ee("hackChance",g.CONSTANTS.ScriptHackAnalyzeRamCost);n("hackChance",g.CONSTANTS.ScriptHackAnalyzeRamCost);const i=ie(e,"hackChance");return Object(y.b)(i)},sleep:function(e){if(t.checkingRam)return 0;if(void 0===e)throw Object(j.d)(t,"sleep() call has incorrect number of arguments. Takes 1 argument");return null==t.disableLogs.ALL&&null==t.disableLogs.sleep&&t.scriptRef.log("Sleeping for "+e+" milliseconds"),Object(j.e)(e,t).then(function(){return Promise.resolve(!0)})},grow:function(e){if(t.checkingRam)return ee("grow",g.CONSTANTS.ScriptGrowRamCost);n("grow",g.CONSTANTS.ScriptGrowRamCost);var i=t.scriptRef.threads;if((isNaN(i)||i<1)&&(i=1),void 0===e)throw Object(j.d)(t,"grow() call has incorrect number of arguments. Takes 1 argument");var a=Object(A.e)(e);if(null==a)throw t.scriptRef.log("Cannot grow(). Invalid IP or hostname passed in: "+e),Object(j.d)(t,"Cannot grow(). Invalid IP or hostname passed in: "+e);if(0==a.hasAdminRights)throw t.scriptRef.log("Cannot grow this server ("+a.hostname+") because user does not have root access"),Object(j.d)(t,"Cannot grow this server ("+a.hostname+") because user does not have root access");var r=Object(y.a)(a);return null==t.disableLogs.ALL&&null==t.disableLogs.grow&&t.scriptRef.log("Executing grow() on server "+a.hostname+" in "+Object(Y.formatNumber)(r,3)+" seconds (t="+i+")"),Object(j.e)(1e3*r,t).then(function(){if(t.env.stopFlag)return Promise.reject(t);const e=a.moneyAvailable<=0?1:a.moneyAvailable;a.moneyAvailable+=1*i;var n=Object(A.k)(a,450*i);const r=a.moneyAvailable;t.scriptRef.recordGrow(a.ip,i);var o=Object(y.c)(a)*i;return 1==n&&(o=0),null==t.disableLogs.ALL&&null==t.disableLogs.grow&&t.scriptRef.log("Available money on "+a.hostname+" grown by "+Object(Y.formatNumber)(r/e*100-100,6)+"%. Gained "+Object(Y.formatNumber)(o,4)+" hacking exp (t="+i+")"),t.scriptRef.onlineExpGained+=o,S.a.gainHackingExp(o),Promise.resolve(r/e)})},growthAnalyze:function(e,i){if(t.checkingRam)return ee("growthAnalyze",g.CONSTANTS.ScriptGrowthAnalyzeRamCost);n("growthAnalyze",g.CONSTANTS.ScriptGrowthAnalyzeRamCost);const a=ie(e,"growthAnalyze");if(isNaN(i))throw Object(j.d)(t,`Invalid growth argument passed into growthAnalyze: ${i}. Must be numeric`);return Object(A.h)(a,Number(i))},weaken:function(e){if(t.checkingRam)return ee("weaken",g.CONSTANTS.ScriptWeakenRamCost);n("weaken",g.CONSTANTS.ScriptWeakenRamCost);var i=t.scriptRef.threads;if((isNaN(i)||i<1)&&(i=1),void 0===e)throw Object(j.d)(t,"weaken() call has incorrect number of arguments. Takes 1 argument");var a=Object(A.e)(e);if(null==a)throw t.scriptRef.log("Cannot weaken(). Invalid IP or hostname passed in: "+e),Object(j.d)(t,"Cannot weaken(). Invalid IP or hostname passed in: "+e);if(0==a.hasAdminRights)throw t.scriptRef.log("Cannot weaken this server ("+a.hostname+") because user does not have root access"),Object(j.d)(t,"Cannot weaken this server ("+a.hostname+") because user does not have root access");var r=Object(y.f)(a);return null==t.disableLogs.ALL&&null==t.disableLogs.weaken&&t.scriptRef.log("Executing weaken() on server "+a.hostname+" in "+Object(Y.formatNumber)(r,3)+" seconds (t="+i+")"),Object(j.e)(1e3*r,t).then(function(){if(t.env.stopFlag)return Promise.reject(t);a.weaken(g.CONSTANTS.ServerWeakenAmount*i),t.scriptRef.recordWeaken(a.ip,i);var e=Object(y.c)(a)*i;return null==t.disableLogs.ALL&&null==t.disableLogs.weaken&&t.scriptRef.log("Server security level on "+a.hostname+" weakened to "+a.hackDifficulty+". Gained "+Object(Y.formatNumber)(e,4)+" hacking exp (t="+i+")"),t.scriptRef.onlineExpGained+=e,S.a.gainHackingExp(e),Promise.resolve(g.CONSTANTS.ServerWeakenAmount*i)})},print:function(e){if(t.checkingRam)return 0;if(void 0===e)throw Object(j.d)(t,"print() call has incorrect number of arguments. Takes 1 argument");t.scriptRef.log(e.toString())},tprint:function(e){if(t.checkingRam)return 0;if(void 0===e||null==e)throw Object(j.d)(t,"tprint() call has incorrect number of arguments. Takes 1 argument");e.toString();Object(H.post)(t.scriptRef.filename+": "+e.toString())},clearLog:function(){if(t.checkingRam)return 0;t.scriptRef.clearLog()},disableLog:function(e){if(t.checkingRam)return 0;if(void 0===ce[e])throw Object(j.d)(t,"Invalid argument to disableLog: "+e);t.disableLogs[e]=!0,null==t.disableLogs.ALL&&null==t.disableLogs.disableLog&&t.scriptRef.log("Disabled logging for "+e)},enableLog:function(e){if(t.checkingRam)return 0;if(void 0===ce[e])throw Object(j.d)(t,"Invalid argument to enableLog: "+e);delete t.disableLogs[e],null==t.disableLogs.ALL&&null==t.disableLogs.enableLog&&t.scriptRef.log("Enabled logging for "+e)},isLogEnabled:function(e){if(t.checkingRam)return 0;if(void 0===ce[e])throw Object(j.d)(t,"Invalid argument to isLogEnabled: "+e);return!t.disableLogs[e]},getScriptLogs:function(e,n){if(t.checkingRam)return 0;if(null!=e&&"string"==typeof e){null==n&&(n=t.serverIp);const i=Object(A.e)(n);if(null==i)throw t.log(`getScriptLogs() failed. Invalid IP or hostname passed in: ${n}`),Object(j.d)(t,`getScriptLogs() failed. Invalid IP or hostname passed in: ${n}`);let a=[];for(let e=2;e{if(void 0===e)throw Object(j.d)(t,"spawn() call has incorrect number of arguments. Usage: spawn(scriptname, numThreads, [arg1], [arg2]...)");if(isNaN(i)||i<0)throw Object(j.d)(t,"Invalid argument for thread count passed into run(). Must be numeric and greater than 0");for(var n=[],a=2;a0,r=i.runningScripts.length-1;r>=0;--r)Object(B.d)(i.runningScripts[r],i.ip);return null==t.disableLogs.ALL&&null==t.disableLogs.killall&&t.scriptRef.log("killall(): Killing all scripts on "+i.hostname+". May take a few minutes for the scripts to die"),a},exit:function(){if(t.checkingRam)return 0;var e=Object(A.e)(t.serverIp);if(null==e)throw Object(j.d)(t,"Error getting Server for this script in exit(). This is a bug please contact game dev");Object(B.d)(t.scriptRef,e.ip)?t.scriptRef.log("Exiting..."):t.scriptRef.log("Exit failed(). This is a bug please contact game developer")},scp:function(e,i,a){if(t.checkingRam)return ee("scp",g.CONSTANTS.ScriptScpRamCost);if(n("scp",g.CONSTANTS.ScriptScpRamCost),2!==arguments.length&&3!==arguments.length)throw Object(j.d)(t,"ERROR: scp() call has incorrect number of arguments. Takes 2 or 3 arguments");if(e&&e.constructor===Array){var r=!1;return e.forEach(function(e){pe(t).scp(e,i,a)&&(r=!0)}),r}if(!e.endsWith(".lit")&&!Object(P.e)(e)&&!e.endsWith("txt"))throw Object(j.d)(t,"ERROR: scp() does not work with this file type. It only works for .script, .lit, and .txt files");var o,s;if(null!=a){if(void 0===e||void 0===i||void 0===a)throw Object(j.d)(t,"ERROR: scp() call has incorrect number of arguments. Takes 2 or 3 arguments");if(null==(o=Object(A.e)(a)))throw Object(j.d)(t,`ERROR: Invalid hostname/ip passed into scp() command: ${a}`);if(null==(s=Object(A.e)(i)))throw Object(j.d)(t,`ERROR: Invalid hostname/ip passed into scp() command: ${i}`)}else{if(null==i)throw Object(j.d)(t,"ERROR: scp() call has incorrect number of arguments. Takes 2 or 3 arguments");if(void 0===e||void 0===i)throw Object(j.d)(t,"ERROR: scp() call has incorrect number of arguments. Takes 2 or 3 arguments");if(null==(o=Object(A.e)(i)))throw Object(j.d)(t,`ERROR: Invalid hostname/ip passed into scp() command: ${i}`);if(null==(s=Object(A.e)(t.serverIp)))throw Object(j.d)(t,"Could not find server ip for this script. This is a bug please contact game developer")}if(e.endsWith(".lit")){for(var l=!1,c=0;c=2&&(r=i.toString());for(var o=[],s=0;sa.maxShares)return t.scriptRef.log(`You cannot purchase this many shares. ${a.symbol} has a maximum of `+`${a.maxShares} shares.`),0;var o=a.playerShares*a.playerAvgPx;S.a.loseMoney(r+g.CONSTANTS.StockMarketCommission);var s=o+r;return a.playerShares+=i,a.playerAvgPx=s/a.playerShares,F.routing.isOn(F.Page.StockMarket)&&Object(R.r)(a),null==t.disableLogs.ALL&&null==t.disableLogs.buyStock&&t.scriptRef.log("Bought "+Object(Y.formatNumber)(i,0)+" shares of "+a.symbol+" at $"+Object(Y.formatNumber)(a.price,2)+" per share"),a.price},sellStock:function(e,i){if(t.checkingRam)return ee("sellStock",g.CONSTANTS.ScriptBuySellStockRamCost);if(n("sellStock",g.CONSTANTS.ScriptBuySellStockRamCost),!S.a.hasTixApiAccess)throw Object(j.d)(t,"You don't have TIX API Access! Cannot use sellStock()");var a=R.e[e];if(null==a)throw Object(j.d)(t,"Invalid stock symbol passed into sellStock()");if(i<0||isNaN(i))return t.scriptRef.log("ERROR: Invalid 'shares' argument passed to sellStock()"),0;if((i=Math.round(i))>a.playerShares&&(i=a.playerShares),0===i)return 0;var r=a.price*i-g.CONSTANTS.StockMarketCommission;S.a.gainMoney(r);var o=(a.price-a.playerAvgPx)*i-g.CONSTANTS.StockMarketCommission;return isNaN(o)&&(o=0),t.scriptRef.onlineMoneyMade+=o,S.a.scriptProdSinceLastAug+=o,S.a.recordMoneySource(o,"stock"),a.playerShares-=i,0==a.playerShares&&(a.playerAvgPx=0),F.routing.isOn(F.Page.StockMarket)&&Object(R.r)(a),null==t.disableLogs.ALL&&null==t.disableLogs.sellStock&&t.scriptRef.log("Sold "+Object(Y.formatNumber)(i,0)+" shares of "+a.symbol+" at $"+Object(Y.formatNumber)(a.price,2)+" per share. Gained $"+Object(Y.formatNumber)(r,2)),a.price},shortStock(e,i){if(t.checkingRam)return ee("shortStock",g.CONSTANTS.ScriptBuySellStockRamCost);if(n("shortStock",g.CONSTANTS.ScriptBuySellStockRamCost),!S.a.hasTixApiAccess)throw Object(j.d)(t,"You don't have TIX API Access! Cannot use shortStock()");if(8!==S.a.bitNodeN&&!(re&&le>=2))throw Object(j.d)(t,"ERROR: Cannot use shortStock(). You must either be in BitNode-8 or you must have Level 2 of Source-File 8");var a=R.e[e];if(null==a)throw Object(j.d)(t,"ERROR: Invalid stock symbol passed into shortStock()");return Object(R.p)(a,i,t)?a.price:0},sellShort(e,i){if(t.checkingRam)return ee("sellShort",g.CONSTANTS.ScriptBuySellStockRamCost);if(n("sellShort",g.CONSTANTS.ScriptBuySellStockRamCost),!S.a.hasTixApiAccess)throw Object(j.d)(t,"You don't have TIX API Access! Cannot use sellShort()");if(8!==S.a.bitNodeN&&!(re&&le>=2))throw Object(j.d)(t,"ERROR: Cannot use sellShort(). You must either be in BitNode-8 or you must have Level 2 of Source-File 8");var a=R.e[e];if(null==a)throw Object(j.d)(t,"ERROR: Invalid stock symbol passed into sellShort()");return Object(R.n)(a,i,t)?a.price:0},placeOrder(e,i,a,r,o){if(t.checkingRam)return ee("placeOrder",g.CONSTANTS.ScriptBuySellStockRamCost);if(n("placeOrder",g.CONSTANTS.ScriptBuySellStockRamCost),!S.a.hasTixApiAccess)throw Object(j.d)(t,"You don't have TIX API Access! Cannot use placeOrder()");if(8!==S.a.bitNodeN&&!(re&&le>=3))throw Object(j.d)(t,"ERROR: Cannot use placeOrder(). You must either be in BitNode-8 or have Level 3 of Source-File 8");var s,l,c=R.e[e];if(null==c)throw Object(j.d)(t,"ERROR: Invalid stock symbol passed into placeOrder()");if((r=r.toLowerCase()).includes("limit")&&r.includes("buy"))s=R.a.LimitBuy;else if(r.includes("limit")&&r.includes("sell"))s=R.a.LimitSell;else if(r.includes("stop")&&r.includes("buy"))s=R.a.StopBuy;else{if(!r.includes("stop")||!r.includes("sell"))throw Object(j.d)(t,"ERROR: Invalid Order Type passed into placeOrder()");s=R.a.StopSell}if((o=o.toLowerCase()).includes("l"))l=R.b.Long;else{if(!o.includes("s"))throw Object(j.d)(t,"ERROR: Invalid Position Type passed into placeOrder()");l=R.b.Short}return Object(R.l)(c,i,a,s,l,t)},cancelOrder(e,i,a,r,o){if(t.checkingRam)return ee("cancelOrder",g.CONSTANTS.ScriptBuySellStockRamCost);if(n("cancelOrder",g.CONSTANTS.ScriptBuySellStockRamCost),!S.a.hasTixApiAccess)throw Object(j.d)(t,"You don't have TIX API Access! Cannot use cancelOrder()");if(8!==S.a.bitNodeN&&!(re&&le>=3))throw Object(j.d)(t,"ERROR: Cannot use cancelOrder(). You must either be in BitNode-8 or have Level 3 of Source-File 8");var s,l,c=R.e[e];if(null==c)throw Object(j.d)(t,"ERROR: Invalid stock symbol passed into cancelOrder()");if(isNaN(i)||isNaN(a))throw Object(j.d)(t,"ERROR: Invalid shares or price argument passed into cancelOrder(). Must be numeric");if((r=r.toLowerCase()).includes("limit")&&r.includes("buy"))s=R.a.LimitBuy;else if(r.includes("limit")&&r.includes("sell"))s=R.a.LimitSell;else if(r.includes("stop")&&r.includes("buy"))s=R.a.StopBuy;else{if(!r.includes("stop")||!r.includes("sell"))throw Object(j.d)(t,"ERROR: Invalid Order Type passed into placeOrder()");s=R.a.StopSell}if((o=o.toLowerCase()).includes("l"))l=R.b.Long;else{if(!o.includes("s"))throw Object(j.d)(t,"ERROR: Invalid Position Type passed into placeOrder()");l=R.b.Short}var u={stock:c,shares:i,price:a,type:s,pos:l};return Object(R.f)(u,t)},getOrders:function(){if(t.checkingRam)return ee("getOrders",g.CONSTANTS.ScriptBuySellStockRamCost);if(n("getOrders",g.CONSTANTS.ScriptBuySellStockRamCost),!S.a.hasTixApiAccess)throw Object(j.d)(t,"You don't have TIX API Access! Cannot use getOrders()");if(8!==S.a.bitNodeN&&!(re&&le>=3))throw Object(j.d)(t,"ERROR: Cannot use getOrders(). You must either be in BitNode-8 or have Level 3 of Source-File 8");const e={},i=R.c.Orders;for(let t in i){const n=i[t];if(n.constructor===Array&&n.length>0){e[t]=[];for(let i=0;i=Object(w.b)())return t.log(`ERROR: You have reached the maximum limit of ${Object(w.b)()} servers. You cannot purchase any more.`),"";const r=Object(w.a)(i);if(r===1/0)return t.log("ERROR: 'purchaseServer()' failed due to an invalid 'ram' argument"),1/0;if(S.a.money.lt(r))return t.log("ERROR: Not enough money to purchase server. Need $"+Object(Y.formatNumber)(r,2)),"";var o=new A.d({ip:Object($.a)(),hostname:a,organizationName:"",isConnectedTo:!1,adminRights:!0,purchasedByPlayer:!0,maxRam:i});Object(A.a)(o),S.a.purchasedServers.push(o.ip);var s=S.a.getHomeComputer();return s.serversOnNetwork.push(o.ip),o.serversOnNetwork.push(s.ip),S.a.loseMoney(r),null==t.disableLogs.ALL&&null==t.disableLogs.purchaseServer&&t.scriptRef.log("Purchased new server with hostname "+o.hostname+" for $"+Object(Y.formatNumber)(r,2)),o.hostname},deleteServer:function(e){if(t.checkingRam)return ee("deleteServer",g.CONSTANTS.ScriptPurchaseServerRamCost);n("deleteServer",g.CONSTANTS.ScriptPurchaseServerRamCost);var i=String(e);i=i.replace(/\s\s+/g,"");var a=Object(A.c)(i);if(null==a)return t.scriptRef.log("ERROR: Could not find server with hostname "+i+". deleteServer() failed"),!1;if(!a.purchasedByPlayer||"home"===a.hostname)return t.scriptRef.log("ERROR: Server "+a.hostname+" is not a purchased server. Cannot be deleted. deleteServer() failed"),!1;var r=a.ip;if(a.isConnectedTo)return t.scriptRef.log("ERROR: deleteServer() failed because you are currently connected to the server you are trying to delete"),!1;if(r===t.serverIp)return t.scriptRef.log("ERROR: Cannot call deleteServer() on self. deleteServer() failed"),!1;if(a.runningScripts.length>0)return t.scriptRef.log("ERROR: Cannot delete server "+a.hostname+" because it still has scripts running."),!1;for(var o=!1,s=0;sg.CONSTANTS.NumNetscriptPorts)throw Object(j.d)(t,"ERROR: Trying to write to invalid port: "+e+". Only ports 1-"+g.CONSTANTS.NumNetscriptPorts+" are valid.");if(null==(e=B.a[e-1])||!(e instanceof W.a))throw Object(j.d)(t,"Could not find port: "+e+". This is a bug contact the game developer");return e.write(i)},tryWrite:function(e,i=""){if(t.checkingRam)return ee("tryWrite",g.CONSTANTS.ScriptReadWriteRamCost);if(n("tryWrite",g.CONSTANTS.ScriptReadWriteRamCost),isNaN(e))throw Object(j.d)(t,"Invalid argument passed in for tryWrite: "+e);if((e=Math.round(e))<1||e>g.CONSTANTS.NumNetscriptPorts)throw Object(j.d)(t,"ERROR: tryWrite() called on invalid port: "+e+". Only ports 1-"+g.CONSTANTS.NumNetscriptPorts+" are valid.");if(null==(e=B.a[e-1])||!(e instanceof W.a))throw Object(j.d)(t,"Could not find port: "+e+". This is a bug contact the game developer");return e.tryWrite(i)},read:function(e){if(t.checkingRam)return ee("read",g.CONSTANTS.ScriptReadWriteRamCost);if(n("read",g.CONSTANTS.ScriptReadWriteRamCost),isNaN(e)){if(Object(z.isString)(e)){let n=e,i=Object(A.e)(t.serverIp);if(null==i)throw Object(j.d)(t,"Error getting Server for this script in read(). This is a bug please contact game dev");if(Object(P.e)(n)){let e=t.getScriptOnServer(n);return null==e?"":e.code}{let e=Object(I.getTextFile)(n,i);return null!==e?e.text:""}}throw Object(j.d)(t,"Invalid argument passed in for read(): "+e)}if((e=Math.round(e))<1||e>g.CONSTANTS.NumNetscriptPorts)throw Object(j.d)(t,"ERROR: Trying to read from invalid port: "+e+". Only ports 1-"+g.CONSTANTS.NumNetscriptPorts+" are valid.");if(null==(e=B.a[e-1])||!(e instanceof W.a))throw Object(j.d)(t,"ERROR: Could not find port: "+e+". This is a bug contact the game developer");return e.read()},peek:function(e){if(t.checkingRam)return ee("peek",g.CONSTANTS.ScriptReadWriteRamCost);if(n("peek",g.CONSTANTS.ScriptReadWriteRamCost),isNaN(e))throw Object(j.d)(t,"ERROR: peek() called with invalid argument. Must be a port number between 1 and "+g.CONSTANTS.NumNetscriptPorts);if((e=Math.round(e))<1||e>g.CONSTANTS.NumNetscriptPorts)throw Object(j.d)(t,"ERROR: peek() called with invalid argument. Must be a port number between 1 and "+g.CONSTANTS.NumNetscriptPorts);if(null==(e=B.a[e-1])||!(e instanceof W.a))throw Object(j.d)(t,"ERROR: Could not find port: "+e+". This is a bug contact the game developer");return e.peek()},clear:function(e){if(t.checkingRam)return ee("clear",g.CONSTANTS.ScriptReadWriteRamCost);if(n("clear",g.CONSTANTS.ScriptReadWriteRamCost),!isNaN(e)){if((e=Math.round(e))<1||e>g.CONSTANTS.NumNetscriptPorts)throw Object(j.d)(t,"ERROR: Trying to clear invalid port: "+e+". Only ports 1-"+g.CONSTANTS.NumNetscriptPorts+" are valid");if(null==(e=B.a[e-1])||!(e instanceof W.a))throw Object(j.d)(t,"ERROR: Could not find port: "+e+". This is a bug contact the game developer");return e.clear()}if(!Object(z.isString)(e))throw Object(j.d)(t,"Invalid argument passed in for clear(): "+e);var i=e,a=Object(A.e)(t.serverIp);if(null==a)throw Object(j.d)(t,"Error getting Server for this script in clear(). This is a bug please contact game dev");var r=Object(I.getTextFile)(i,a);return null!=r&&r.write(""),0},getPortHandle:function(e){if(t.checkingRam)return ee("getPortHandle",10*g.CONSTANTS.ScriptReadWriteRamCost);if(n("getPortHandle",10*g.CONSTANTS.ScriptReadWriteRamCost),isNaN(e))throw Object(j.d)(t,"ERROR: Invalid argument passed into getPortHandle(). Must be an integer between 1 and "+g.CONSTANTS.NumNetscriptPorts);if((e=Math.round(e))<1||e>g.CONSTANTS.NumNetscriptPorts)throw Object(j.d)(t,"ERROR: getPortHandle() called with invalid port number: "+e+". Only ports 1-"+g.CONSTANTS.NumNetscriptPorts+" are valid");if(null==(e=B.a[e-1])||!(e instanceof W.a))throw Object(j.d)(t,"ERROR: Could not find port: "+e+". This is a bug contact the game developer");return e},rm:function(e,i){if(t.checkingRam)return ee("rm",g.CONSTANTS.ScriptReadWriteRamCost);n("rm",g.CONSTANTS.ScriptReadWriteRamCost),null!=i&&""!==i||(i=t.serverIp);var a=Object(A.e)(i);if(null==a)throw Object(j.d)(t,`Invalid server specified for rm(): ${i}`);if(e.endsWith(".exe")){for(var r=0;r{Object(X.removeElementById)(n),e(!0)}}),r=Object(V.createElement)("button",{class:"popup-box-button",innerText:"No",clickListener:()=>{Object(X.removeElementById)(n),e(!1)}});Object(J.createPopup)(n,[i,a,r])})},wget:async function(n,i,a=t.serverIp){if(t.checkingRam)return 0;if(!Object(P.e)(i)&&!i.endsWith(".txt"))return workerSript.log(`ERROR: wget() failed because of an invalid target file: ${i}. Target file must be a script or text file`),Promise.resolve(!1);var r=ie(a,"wget");return new Promise(function(o,s){e.get(n,function(e){let n;return(n=Object(P.e)(i)?r.writeToScriptFile(i,e):r.writeToTextFile(i,e)).success?n.overwritten?(t.log(`wget() successfully retrieved content and overwrote ${i} on ${a}`),o(!0)):(t.log(`wget successfully retrieved content to new file ${i} on ${a}`),o(!0)):(t.log("ERROR: wget() failed"),o(!1))},"text").fail(function(e){return t.log(`ERROR: wget() failed: ${JSON.stringify(e)}`),o(!1)})})},getFavorToDonate:function(){return t.checkingRam?ee("getFavorToDonate",g.CONSTANTS.ScriptGetFavorToDonate):(n("getFavorToDonate",g.CONSTANTS.ScriptGetFavorToDonate),Math.floor(g.CONSTANTS.BaseFavorToDonate*l.BitNodeMultipliers.RepToDonateToFaction))},universityCourse:function(e,i){var a=g.CONSTANTS.ScriptSingularityFn1RamCost;if(4!==S.a.bitNodeN&&(a*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("universityCourse",a);if(n("universityCourse",a),4!=S.a.bitNodeN&&!(te&&se>=1))throw Object(j.d)(t,"Cannot run universityCourse(). It is a Singularity Function and requires SourceFile-4 (level 1) to run.");if(!T.c){if(S.a.isWorking){var r=S.a.singularityStopWork();null==t.disableLogs.ALL&&null==t.disableLogs.universityCourse&&t.scriptRef.log(r)}var o,s,l;switch(e.toLowerCase()){case C.Locations.AevumSummitUniversity.toLowerCase():if(S.a.city!=C.Locations.Aevum)return t.scriptRef.log("ERROR: You cannot study at Summit University because you are not in Aevum. universityCourse() failed"),!1;S.a.location=C.Locations.AevumSummitUniversity,o=4,s=3;break;case C.Locations.Sector12RothmanUniversity.toLowerCase():if(S.a.city!=C.Locations.Sector12)return t.scriptRef.log("ERROR: You cannot study at Rothman University because you are not in Sector-12. universityCourse() failed"),!1;S.a.location=C.Locations.Sector12RothmanUniversity,o=3,s=2;break;case C.Locations.VolhavenZBInstituteOfTechnology.toLowerCase():if(S.a.city!=C.Locations.Volhaven)return t.scriptRef.log("ERROR: You cannot study at ZB Institute of Technology because you are not in Volhaven. universityCourse() failed"),!1;S.a.location=C.Locations.VolhavenZBInstituteOfTechnology,o=5,s=4;break;default:return t.scriptRef.log("Invalid university name: "+e+". universityCourse() failed"),!1}switch(i.toLowerCase()){case"Study Computer Science".toLowerCase():l=g.CONSTANTS.ClassStudyComputerScience;break;case"Data Structures".toLowerCase():l=g.CONSTANTS.ClassDataStructures;break;case"Networks".toLowerCase():l=g.CONSTANTS.ClassNetworks;break;case"Algorithms".toLowerCase():l=g.CONSTANTS.ClassAlgorithms;break;case"Management".toLowerCase():l=g.CONSTANTS.ClassManagement;break;case"Leadership".toLowerCase():l=g.CONSTANTS.ClassLeadership;break;default:return t.scriptRef.log("Invalid class name: "+i+". universityCourse() failed"),!1}return S.a.startClass(o,s,l),null==t.disableLogs.ALL&&null==t.disableLogs.universityCourse&&t.scriptRef.log("Started "+l+" at "+e),!0}t.scriptRef.log("ERROR: universityCourse() failed because you are in the middle of a mission.")},gymWorkout:function(e,i){var a=g.CONSTANTS.ScriptSingularityFn1RamCost;if(4!==S.a.bitNodeN&&(a*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("gymWorkout",a);if(n("gymWorkout",a),4!=S.a.bitNodeN&&!(te&&se>=1))throw Object(j.d)(t,"Cannot run gymWorkout(). It is a Singularity Function and requires SourceFile-4 (level 1) to run.");if(!T.c){if(S.a.isWorking){var r=S.a.singularityStopWork();null==t.disableLogs.ALL&&null==t.disableLogs.gymWorkout&&t.scriptRef.log(r)}var o,s;switch(e.toLowerCase()){case C.Locations.AevumCrushFitnessGym.toLowerCase():if(S.a.city!=C.Locations.Aevum)return t.scriptRef.log("ERROR: You cannot workout at Crush Fitness because you are not in Aevum. gymWorkout() failed"),!1;S.a.location=C.Locations.AevumCrushFitnessGym,o=3,s=2;break;case C.Locations.AevumSnapFitnessGym.toLowerCase():if(S.a.city!=C.Locations.Aevum)return t.scriptRef.log("ERROR: You cannot workout at Snap Fitness because you are not in Aevum. gymWorkout() failed"),!1;S.a.location=C.Locations.AevumSnapFitnessGym,o=10,s=5;break;case C.Locations.Sector12IronGym.toLowerCase():if(S.a.city!=C.Locations.Sector12)return t.scriptRef.log("ERROR: You cannot workout at Iron Gym because you are not in Sector-12. gymWorkout() failed"),!1;S.a.location=C.Locations.Sector12IronGym,o=1,s=1;break;case C.Locations.Sector12PowerhouseGym.toLowerCase():if(S.a.city!=C.Locations.Sector12)return t.scriptRef.log("ERROR: You cannot workout at Powerhouse Gym because you are not in Sector-12. gymWorkout() failed"),!1;S.a.location=C.Locations.Sector12PowerhouseGym,o=20,s=10;break;case C.Locations.VolhavenMilleniumFitnessGym.toLowerCase():if(S.a.city!=C.Locations.Volhaven)return t.scriptRef.log("ERROR: You cannot workout at Millenium Fitness Gym because you are not in Volhaven. gymWorkout() failed"),!1;S.a.location=C.Locations.VolhavenMilleniumFitnessGym,o=7,s=4;break;default:return t.scriptRef.log("Invalid gym name: "+e+". gymWorkout() failed"),!1}switch(i.toLowerCase()){case"strength".toLowerCase():case"str".toLowerCase():S.a.startClass(o,s,g.CONSTANTS.ClassGymStrength);break;case"defense".toLowerCase():case"def".toLowerCase():S.a.startClass(o,s,g.CONSTANTS.ClassGymDefense);break;case"dexterity".toLowerCase():case"dex".toLowerCase():S.a.startClass(o,s,g.CONSTANTS.ClassGymDexterity);break;case"agility".toLowerCase():case"agi".toLowerCase():S.a.startClass(o,s,g.CONSTANTS.ClassGymAgility);break;default:return t.scriptRef.log("Invalid stat: "+i+". gymWorkout() failed"),!1}return null==t.disableLogs.ALL&&null==t.disableLogs.gymWorkout&&t.scriptRef.log("Started training "+i+" at "+e),!0}t.scriptRef.log("ERROR: gymWorkout() failed because you are in the middle of a mission.")},travelToCity(e){var i=g.CONSTANTS.ScriptSingularityFn1RamCost;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("travelToCity",i);if(n("travelToCity",i),4!=S.a.bitNodeN&&!(te&&se>=1))throw Object(j.d)(t,"Cannot run travelToCity(). It is a Singularity Function and requires SourceFile-4 (level 1) to run.");switch(e){case C.Locations.Aevum:case C.Locations.Chongqing:case C.Locations.Sector12:case C.Locations.NewTokyo:case C.Locations.Ishima:case C.Locations.Volhaven:if(S.a.money.lt(g.CONSTANTS.TravelCost))throw t.scriptRef.log("ERROR: not enough money to travel with travelToCity()."),Object(j.d)(t,"ERROR: not enough money to travel with travelToCity().");return S.a.loseMoney(g.CONSTANTS.TravelCost),S.a.city=e,null==t.disableLogs.ALL&&null==t.disableLogs.travelToCity&&t.scriptRef.log("Traveled to "+e),!0;default:return t.scriptRef.log("ERROR: Invalid city name passed into travelToCity()."),!1}},purchaseTor(){var e=g.CONSTANTS.ScriptSingularityFn1RamCost;if(4!==S.a.bitNodeN&&(e*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("purchaseTor",e);if(n("purchaseTor",e),4!=S.a.bitNodeN&&!(te&&se>=1))throw Object(j.d)(t,"Cannot run purchaseTor(). It is a Singularity Function and requires SourceFile-4 (level 1) to run.");if(null!=x.a["Darkweb Server"])return t.scriptRef.log("You already have a TOR router! purchaseTor() failed"),!1;if(S.a.money.lt(g.CONSTANTS.TorRouterCost))return t.scriptRef.log("ERROR: You cannot afford to purchase a Tor router. purchaseTor() failed"),!1;S.a.loseMoney(g.CONSTANTS.TorRouterCost);var i=new A.d({ip:Object($.a)(),hostname:"darkweb",organizationName:"",isConnectedTo:!1,adminRights:!1,purchasedByPlayer:!1,maxRam:1});Object(A.a)(i),x.a.addIp("Darkweb Server",i.ip);const a=document.getElementById("location-purchase-tor");return a.setAttribute("class","a-link-button-bought"),a.innerHTML="TOR Router - Purchased",S.a.getHomeComputer().serversOnNetwork.push(i.ip),i.serversOnNetwork.push(S.a.getHomeComputer().ip),S.a.gainIntelligenceExp(g.CONSTANTS.IntelligenceSingFnBaseExpGain),null==t.disableLogs.ALL&&null==t.disableLogs.purchaseTor&&t.scriptRef.log("You have purchased a Tor router!"),!0},purchaseProgram(e){var i=g.CONSTANTS.ScriptSingularityFn1RamCost;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("purchaseProgram",i);if(n("purchaseProgram",i),4!=S.a.bitNodeN&&!(te&&se>=1))throw Object(j.d)(t,"Cannot run purchaseProgram(). It is a Singularity Function and requires SourceFile-4 (level 1) to run.");if(null==x.a["Darkweb Server"])return t.scriptRef.log("ERROR: You do not have the TOR router. purchaseProgram() failed."),!1;e=e.toLowerCase();let a=null;for(const t in _.DarkWebItems){const n=_.DarkWebItems[t];n.program.toLowerCase()==e&&(a=n)}return null==a?(t.scriptRef.log("ERROR: Invalid program name passed into purchaseProgram()."),!1):S.a.money.lt(a.price)?(t.scriptRef.log("Not enough money to purchase "+a.program),!1):S.a.hasProgram(a.program)?(t.scriptRef.log("You already have the "+a.program+" program"),!0):(S.a.loseMoney(a.price),S.a.getHomeComputer().programs.push(a.program),null==t.disableLogs.ALL&&null==t.disableLogs.purchaseProgram&&t.scriptRef.log("You have purchased the "+a.program+" program. The new program can be found on your home computer."),!0)},getStats:function(){var e=g.CONSTANTS.ScriptSingularityFn1RamCost/4;if(4!==S.a.bitNodeN&&(e*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getStats",e);if(n("getStats",e),4!=S.a.bitNodeN&&!(te&&se>=1))throw Object(j.d)(t,"Cannot run getStats(). It is a Singularity Function and requires SourceFile-4 (level 1) to run.");return{hacking:S.a.hacking_skill,strength:S.a.strength,defense:S.a.defense,dexterity:S.a.dexterity,agility:S.a.agility,charisma:S.a.charisma,intelligence:S.a.intelligence}},getCharacterInformation:function(){var e=g.CONSTANTS.ScriptSingularityFn1RamCost/4;if(4!==S.a.bitNodeN&&(e*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getCharacterInformation",e);if(n("getCharacterInformation",e),4!=S.a.bitNodeN&&!(te&&se>=1))throw Object(j.d)(t,"Cannot run getCharacterInformation(). It is a Singularity Function and requires SourceFile-4 (level 1) to run.");return{bitnode:S.a.bitNodeN,city:S.a.city,factions:S.a.factions.slice(),hp:S.a.hp,jobs:Object.keys(S.a.jobs),jobTitles:Object.values(S.a.jobs),maxHp:S.a.max_hp,mult:{agility:S.a.agility_mult,agilityExp:S.a.agility_exp_mult,companyRep:S.a.company_rep_mult,crimeMoney:S.a.crime_money_mult,crimeSuccess:S.a.crime_success_mult,defense:S.a.defense_mult,defenseExp:S.a.defense_exp_mult,dexterity:S.a.dexterity_mult,dexterityExp:S.a.dexterity_exp_mult,factionRep:S.a.faction_rep_mult,hacking:S.a.hacking_mult,hackingExp:S.a.hacking_exp_mult,strength:S.a.strength_mult,strengthExp:S.a.strength_exp_mult,workMoney:S.a.work_money_mult},timeWorked:S.a.timeWorked,tor:x.a.hasOwnProperty("Darkweb Server"),workHackExpGain:S.a.workHackExpGained,workStrExpGain:S.a.workStrExpGained,workDefExpGain:S.a.workDefExpGained,workDexExpGain:S.a.workDexExpGained,workAgiExpGain:S.a.workAgiExpGained,workChaExpGain:S.a.workChaExpGained,workRepGain:S.a.workRepGained,workMoneyGain:S.a.workMoneyGained}},isBusy:function(){var e=g.CONSTANTS.ScriptSingularityFn1RamCost/4;if(4!==S.a.bitNodeN&&(e*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("isBusy",e);if(n("isBusy",e),4!=S.a.bitNodeN&&!(te&&se>=1))throw Object(j.d)(t,"Cannot run isBusy(). It is a Singularity Function and requires SourceFile-4 (level 1) to run.");return S.a.isWorking},stopAction:function(){var e=g.CONSTANTS.ScriptSingularityFn1RamCost/2;if(4!==S.a.bitNodeN&&(e*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("stopAction",e);if(n("stopAction",e),4!=S.a.bitNodeN&&!(te&&se>=1))throw Object(j.d)(t,"Cannot run stopAction(). It is a Singularity Function and requires SourceFile-4 (level 1) to run.");if(S.a.isWorking){var i=S.a.singularityStopWork();return null==t.disableLogs.ALL&&null==t.disableLogs.stopAction&&t.scriptRef.log(i),!0}return!1},upgradeHomeRam:function(){var e=g.CONSTANTS.ScriptSingularityFn2RamCost;if(4!==S.a.bitNodeN&&(e*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("upgradeHomeRam",e);if(n("upgradeHomeRam",e),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run upgradeHomeRam(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");const i=S.a.getHomeComputer();if(i.maxRam>=g.CONSTANTS.HomeComputerMaxRam)return t.log("ERROR: upgradeHomeRam() failed because your home computer is at max RAM"),!1;const a=S.a.getUpgradeHomeRamCost();return S.a.money.lt(a)?(t.scriptRef.log("ERROR: upgradeHomeRam() failed because you don't have enough money"),!1):(i.maxRam*=2,S.a.loseMoney(a),S.a.gainIntelligenceExp(g.CONSTANTS.IntelligenceSingFnBaseExpGain),null==t.disableLogs.ALL&&null==t.disableLogs.upgradeHomeRam&&t.scriptRef.log("Purchased additional RAM for home computer! It now has "+i.maxRam+"GB of RAM."),!0)},getUpgradeHomeRamCost:function(){var e=g.CONSTANTS.ScriptSingularityFn2RamCost/2;if(4!==S.a.bitNodeN&&(e*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getUpgradeHomeRamCost",e);if(n("getUpgradeHomeRamCost",e),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run getUpgradeHomeRamCost(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");return S.a.getUpgradeHomeRamCost()},workForCompany:function(e){var i=g.CONSTANTS.ScriptSingularityFn2RamCost;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("workForCompany",i);if(n("workForCompany",i),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run workForCompany(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");if(null==e&&(e=S.a.companyName),null==e||""===e||!(m.Companies[e]instanceof p.Company))return t.scriptRef.log(`ERROR: workForCompany() failed because of an invalid company specified: ${e}`),!1;if(!Object.keys(S.a.jobs).includes(e))return t.scriptRef.log(`ERROR: workForCompany() failed because you do not have a job at ${e}`),!1;if(T.c)return t.scriptRef.log("ERROR: workForCompany() failed because you are in the middle of a mission."),!1;const a=S.a.jobs[e],r=h.CompanyPositions[a];if(""===a||!(r instanceof d.CompanyPosition))return t.scriptRef.log("ERROR: workForCompany() failed because you do not have a job"),!1;if(S.a.isWorking){var o=S.a.singularityStopWork();null==t.disableLogs.ALL&&null==t.disableLogs.workForCompany&&t.scriptRef.log(o)}return r.isPartTimeJob()?S.a.startWorkPartTime(e):S.a.startWork(e),null==t.disableLogs.ALL&&null==t.disableLogs.workForCompany&&t.log(`Began working at ${S.a.companyName} as a ${a}`),!0},applyToCompany:function(e,i){var a,r=g.CONSTANTS.ScriptSingularityFn2RamCost;if(4!==S.a.bitNodeN&&(r*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("applyToCompany",r);if(n("applyToCompany",r),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run applyToCompany(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");if(!Object(m.companyExists)(e))return t.scriptRef.log("ERROR: applyToCompany() failed because specified company "+e+" does not exist."),!1;switch(S.a.location=e,i.toLowerCase()){case"software":a=S.a.applyForSoftwareJob(!0);break;case"software consultant":a=S.a.applyForSoftwareConsultantJob(!0);break;case"it":a=S.a.applyForItJob(!0);break;case"security engineer":a=S.a.applyForSecurityEngineerJob(!0);break;case"network engineer":a=S.a.applyForNetworkEngineerJob(!0);break;case"business":a=S.a.applyForBusinessJob(!0);break;case"business consultant":a=S.a.applyForBusinessConsultantJob(!0);break;case"security":a=S.a.applyForSecurityJob(!0);break;case"agent":a=S.a.applyForAgentJob(!0);break;case"employee":a=S.a.applyForEmployeeJob(!0);break;case"part-time employee":a=S.a.applyForPartTimeEmployeeJob(!0);break;case"waiter":a=S.a.applyForWaiterJob(!0);break;case"part-time waiter":a=S.a.applyForPartTimeWaiterJob(!0);break;default:return t.scriptRef.log("ERROR: Invalid job passed into applyToCompany: "+i+". applyToCompany() failed"),!1}return Object(z.isString)(a)?(t.scriptRef.log(a),!1):(a?null==t.disableLogs.ALL&&null==t.disableLogs.applyToCompany&&t.log(`You were offered a new job at ${e} as a ${S.a.jobs[e]}`):null==t.disableLogs.ALL&&null==t.disableLogs.applyToCompany&&t.log(`You failed to get a new job/promotion at ${e} in the ${i} field.`),a)},getCompanyRep:function(e){var i=g.CONSTANTS.ScriptSingularityFn2RamCost/2;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getCompanyRep",i);if(n("getCompanyRep",i),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run getCompanyRep(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");var a=m.Companies[e];return null!=a&&a instanceof p.Company?a.playerReputation:(t.scriptRef.log("ERROR: Invalid companyName passed into getCompanyRep(): "+e),-1)},getCompanyFavor:function(e){var i=g.CONSTANTS.ScriptSingularityFn2RamCost/4;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getCompanyFavor",i);if(n("getCompanyFavor",i),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run getCompanyFavor(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");var a=m.Companies[e];return null!=a&&a instanceof p.Company?a.favor:(t.scriptRef.log("ERROR: Invalid companyName passed into getCompanyFavor(): "+e),-1)},getCompanyFavorGain:function(e){var i=g.CONSTANTS.ScriptSingularityFn2RamCost/4;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getCompanyFavorGain",i);if(n("getCompanyFavorGain",i),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run getCompanyFavorGain(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");var a=m.Companies[e];return null!=a&&a instanceof p.Company?a.getFavorGain()[0]:(t.scriptRef.log("ERROR: Invalid companyName passed into getCompanyFavorGain(): "+e),-1)},checkFactionInvitations:function(){var e=g.CONSTANTS.ScriptSingularityFn2RamCost;if(4!==S.a.bitNodeN&&(e*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("checkFactionInvitations",e);if(n("checkFactionInvitations",e),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run checkFactionInvitations(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");return S.a.factionInvitations.slice()},joinFaction:function(e){var i=g.CONSTANTS.ScriptSingularityFn2RamCost;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("joinFaction",i);if(n("joinFaction",i),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run joinFaction(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");if(!Object(E.factionExists)(e))return t.scriptRef.log("ERROR: Faction specified in joinFaction() does not exist."),!1;if(!S.a.factionInvitations.includes(e))return t.scriptRef.log("ERROR: Cannot join "+e+" Faction because you have not been invited. joinFaction() failed"),!1;var a=E.Factions[e];Object(v.c)(a);for(var r=0;r=2))throw Object(j.d)(t,"Cannot run workForFaction(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");if(S.a.inGang()&&void 0!==f.a[e])t.scriptRef.log("ERROR: Faction specified in workForFaction() does not offer work at the moment.");else{if(!T.c){if(!Object(E.factionExists)(e))return t.scriptRef.log("ERROR: Faction specified in workForFaction() does not exist."),!1;if(!S.a.factions.includes(e))return t.scriptRef.log("ERROR: workForFaction() failed because you are not a member of "+e),!1;if(S.a.isWorking){var r=S.a.singularityStopWork();null==t.disableLogs.ALL&&null==t.disableLogs.workForFaction&&t.scriptRef.log(r)}var o=E.Factions[e];switch(i.toLowerCase()){case"hacking":case"hacking contracts":case"hackingcontracts":return["Illuminati","Daedalus","The Covenant","ECorp","MegaCorp","Bachman & Associates","Blade Industries","NWO","Clarke Incorporated","OmniTek Incorporated","Four Sigma","KuaiGong International","Fulcrum Secret Technologies","BitRunners","The Black Hand","NiteSec","Chongqing","Sector-12","New Tokyo","Aevum","Ishima","Volhaven","Speakers for the Dead","The Dark Army","The Syndicate","Silhouette","Netburners","Tian Di Hui","CyberSec"].includes(o.name)?(S.a.startFactionHackWork(o),t.scriptRef.log("Started carrying out hacking contracts for "+o.name),!0):(t.scriptRef.log("ERROR: Cannot carry out hacking contracts for "+o.name+". workForFaction() failed"),!1);case"field":case"fieldwork":case"field work":return["Illuminati","Daedalus","The Covenant","ECorp","MegaCorp","Bachman & Associates","Blade Industries","NWO","Clarke Incorporated","OmniTek Incorporated","Four Sigma","KuaiGong International","The Black Hand","Chongqing","Sector-12","New Tokyo","Aevum","Ishima","Volhaven","Speakers for the Dead","The Dark Army","The Syndicate","Silhouette","Tetrads","Slum Snakes"].includes(o.name)?(S.a.startFactionFieldWork(o),t.scriptRef.log("Started carrying out field missions for "+o.name),!0):(t.scriptRef.log("ERROR: Cannot carry out field missions for "+o.name+". workForFaction() failed"),!1);case"security":case"securitywork":case"security work":return["ECorp","MegaCorp","Bachman & Associates","Blade Industries","NWO","Clarke Incorporated","OmniTek Incorporated","Four Sigma","KuaiGong International","Fulcrum Secret Technologies","Chongqing","Sector-12","New Tokyo","Aevum","Ishima","Volhaven","Speakers for the Dead","The Syndicate","Tetrads","Slum Snakes","Tian Di Hui"].includes(o.name)?(S.a.startFactionSecurityWork(o),t.scriptRef.log("Started serving as security details for "+o.name),!0):(t.scriptRef.log("ERROR: Cannot serve as security detail for "+o.name+". workForFaction() failed"),!1);default:t.scriptRef.log("ERROR: Invalid work type passed into workForFaction(): "+i)}return!0}t.scriptRef.log("ERROR: workForFaction() failed because you are in the middle of a mission.")}},getFactionRep:function(e){var i=g.CONSTANTS.ScriptSingularityFn2RamCost/4;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getFactionRep",i);if(n("getFactionRep",i),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run getFactionRep(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");return Object(E.factionExists)(e)?E.Factions[e].playerReputation:(t.scriptRef.log("ERROR: Faction specified in getFactionRep() does not exist."),-1)},getFactionFavor:function(e){var i=g.CONSTANTS.ScriptSingularityFn2RamCost;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getFactionFavor",i);if(n("getFactionFavor",i),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run getFactionFavor(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");return Object(E.factionExists)(e)?E.Factions[e].favor:(t.scriptRef.log("ERROR: Faction specified in getFactionFavor() does not exist."),-1)},getFactionFavorGain:function(e){var i=g.CONSTANTS.ScriptSingularityFn2RamCost;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getFactionFavorGain",i);if(n("getFactionFavorGain",i),4!=S.a.bitNodeN&&!(te&&se>=2))throw Object(j.d)(t,"Cannot run getFactionFavorGain(). It is a Singularity Function and requires SourceFile-4 (level 2) to run.");return Object(E.factionExists)(e)?E.Factions[e].getFavorGain()[0]:(t.scriptRef.log("ERROR: Faction specified in getFactionFavorGain() does not exist."),-1)},donateToFaction:function(e,i){var a=g.CONSTANTS.ScriptSingularityFn3RamCost;if(4!==S.a.bitNodeN&&(a*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("donateToFaction",a);if(n("donateToFaction",a),4!=S.a.bitNodeN&&!(te&&se>=3))throw Object(j.d)(t,"Cannot run donateToFaction(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");if(!Object(E.factionExists)(e))return t.log(`ERROR: Faction specified in donateToFaction() does not exist: ${e}`),!1;if("number"!=typeof i||i<=0)return t.log(`ERROR: Invalid donation amount specified in donateToFaction(): ${i}. Must be numeric and positive`),!1;if(S.a.money.lt(i))return t.log(`ERROR: You do not have enough money to donate $${i} to ${e}`),!1;var r=Math.round(g.CONSTANTS.BaseFavorToDonate*l.BitNodeMultipliers.RepToDonateToFaction);if(E.Factions[e].favor=3))throw Object(j.d)(t,"Cannot run createProgram(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");if(T.c)return void t.scriptRef.log("ERROR: createProgram() failed because you are in the middle of a mission.");if(S.a.isWorking){var a=S.a.singularityStopWork();null==t.disableLogs.ALL&&null==t.disableLogs.createProgram&&t.scriptRef.log(a)}e=e.toLowerCase();let r=null;for(const t in M.Programs)M.Programs[t].name.toLowerCase()==e&&(r=M.Programs[t]);return null==r?(t.scriptRef.log("ERROR: createProgram() failed because the specified program does not exist: "+e),!1):S.a.hasProgram(r.name)?(t.scriptRef.log("ERROR: createProgram() failed because you already have the "+r.name+" program"),!1):r.create.req(S.a)?(S.a.startCreateProgramWork(r.name,r.create.time,r.create.level),null==t.disableLogs.ALL&&null==t.disableLogs.createProgram&&t.scriptRef.log("Began creating program: "+e),!0):(t.scriptRef.log("ERROR: createProgram() failed because hacking level is too low to create "+r.name+" (level "+r.create.level+" req)"),!1)},commitCrime:function(e){var i=g.CONSTANTS.ScriptSingularityFn3RamCost;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("commitCrime",i);if(n("commitCrime",i),4!=S.a.bitNodeN&&!(te&&se>=3))throw Object(j.d)(t,"Cannot run commitCrime(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");if(T.c)return void t.scriptRef.log("ERROR: commitCrime() failed because you are in the middle of a mission.");if(S.a.isWorking){var a=S.a.singularityStopWork();null==t.disableLogs.ALL&&null==t.disableLogs.commitCrime&&t.scriptRef.log(a)}switch(S.a.city){case C.Locations.Aevum:S.a.location=C.Locations.AevumSlums;break;case C.Locations.Chongqing:S.a.location=C.Locations.ChongqingSlums;break;case C.Locations.Sector12:S.a.location=C.Locations.Sector12Slums;break;case C.Locations.NewTokyo:S.a.location=C.Locations.NewTokyoSlums;break;case C.Locations.Ishima:S.a.location=C.Locations.IshimaSlums;break;case C.Locations.Volhaven:S.a.location=C.Locations.VolhavenSlums;break;default:console.log("Invalid Player.city value")}const r=Object(c.findCrime)(e.toLowerCase());if(null==r)throw Object(j.d)(t,"Invalid crime passed into commitCrime(): "+e);return null==t.disableLogs.ALL&&null==t.disableLogs.commitCrime&&t.scriptRef.log("Attempting to commit crime: "+r.name+"..."),r.commit(S.a,1,{workerscript:t})},getCrimeChance:function(e){var i=g.CONSTANTS.ScriptSingularityFn3RamCost;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getCrimeChance",i);if(n("getCrimeChance",i),4!=S.a.bitNodeN&&!(te&&se>=3))throw Object(j.d)(t,"Cannot run getCrimeChance(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");const a=Object(c.findCrime)(e.toLowerCase());if(null==a)throw Object(j.d)(t,"Invalid crime passed into getCrimeChance(): "+a);return a.successRate(S.a)},getOwnedAugmentations:function(e=!1){var i=g.CONSTANTS.ScriptSingularityFn3RamCost;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getOwnedAugmentations",i);if(n("getOwnedAugmentations",i),4!=S.a.bitNodeN&&!(te&&se>=3))throw Object(j.d)(t,"Cannot run getOwnedAugmentations(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");for(var a=[],r=0;r=3))throw Object(j.d)(t,"Cannot run getOwnedSourceFiles(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");let i=[];for(let e=0;e=3))throw Object(j.d)(t,"Cannot run getAugmentationsFromFaction(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");if(!Object(E.factionExists)(e))return t.scriptRef.log("ERROR: getAugmentationsFromFaction() failed. Invalid faction name passed in (this is case-sensitive): "+e),[];for(var a=E.Factions[e],r=[],o=0;o=3))throw Object(j.d)(t,"Cannot run getAugmentationPrereq(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");return Object(o.b)(e)?r.Augmentations[e].prereqs.slice():(t.scriptRef.log("ERROR: getAugmentationPrereq() failed. Invalid Augmentation name passed in (note: this is case-sensitive): "+e),[])},getAugmentationCost:function(e){var i=g.CONSTANTS.ScriptSingularityFn3RamCost;if(4!==S.a.bitNodeN&&(i*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("getAugmentationCost",i);if(n("getAugmentationCost",i),4!=S.a.bitNodeN&&!(te&&se>=3))throw Object(j.d)(t,"Cannot run getAugmentationCost(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");if(!Object(o.b)(e))return t.scriptRef.log("ERROR: getAugmentationCost() failed. Invalid Augmentation name passed in (note: this is case-sensitive): "+e),[-1,-1];var a=r.Augmentations[e];return[a.baseRepRequirement,a.baseCost]},purchaseAugmentation:function(e,i){var o=g.CONSTANTS.ScriptSingularityFn3RamCost;if(4!==S.a.bitNodeN&&(o*=g.CONSTANTS.ScriptSingularityFnRamMult),t.checkingRam)return ee("purchaseAugmentation",o);if(n("purchaseAugmentation",o),4!=S.a.bitNodeN&&!(te&&se>=3))throw Object(j.d)(t,"Cannot run purchaseAugmentation(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");var l=E.Factions[e];if(null==l||!(l instanceof b.Faction))return t.scriptRef.log("ERROR: purchaseAugmentation() failed because of invalid faction name: "+e),!1;if(!l.augmentations.includes(i))return t.scriptRef.log("ERROR: purchaseAugmentation() failed because the faction "+e+" does not contain the "+i+" augmentation"),!1;var c=r.Augmentations[i];if(null==c||!(c instanceof a.Augmentation))return t.scriptRef.log("ERROR: purchaseAugmentation() failed because of invalid augmentation name: "+i),!1;var u=!1;if(c.name===s.AugmentationNames.NeuroFluxGovernor&&(u=!0),!u){for(var p=0;p=3))throw Object(j.d)(t,"Cannot run installAugmentations(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");return 0===S.a.queuedAugmentations.length?(t.scriptRef.log("ERROR: installAugmentations() failed because you do not have any Augmentations to be installed"),!1):(S.a.gainIntelligenceExp(g.CONSTANTS.IntelligenceSingFnBaseExpGain),t.scriptRef.log("Installing Augmentations. This will cause this script to be killed"),Object(o.e)(e),!0)},gang:{getMemberNames:function(){if(t.checkingRam)return ee("getMemberNames",g.CONSTANTS.ScriptGangApiBaseRamCost/4);n("getMemberNames",g.CONSTANTS.ScriptGangApiBaseRamCost/4),D.a(t,"getMemberNames");try{const e=[];for(const t of S.a.gang.members)e.push(t.name);return e}catch(e){throw Object(j.d)(t,D.b("getMemberNames",e))}},getGangInformation:function(){if(t.checkingRam)return ee("getGangInformation",g.CONSTANTS.ScriptGangApiBaseRamCost/2);n("getGangInformation",g.CONSTANTS.ScriptGangApiBaseRamCost/2),D.a(t,"getGangInformation");try{return{faction:S.a.gang.facName,isHacking:S.a.gang.isHackingGang,moneyGainRate:S.a.gang.moneyGainRate,power:S.a.gang.getPower(),respect:S.a.gang.respect,respectGainRate:S.a.gang.respectGainRate,territory:S.a.gang.getTerritory(),territoryClashChance:S.a.gang.territoryClashChance,territoryWarfareEngaged:S.a.gang.territoryWarfareEngaged,wantedLevel:S.a.gang.wanted,wantedLevelGainRate:S.a.gang.wantedGainRate}}catch(e){throw Object(j.d)(t,D.b("getGangInformation",e))}},getOtherGangInformation:function(){if(t.checkingRam)return ee("getOtherGangInformation",g.CONSTANTS.ScriptGangApiBaseRamCost/2);n("getOtherGangInformation",g.CONSTANTS.ScriptGangApiBaseRamCost/2),D.a(t,"getOtherGangInformation");try{return Object.assign(f.a)}catch(e){throw Object(j.d)(t,D.b("getOtherGangInformation",e))}},getMemberInformation:function(e){if(t.checkingRam)return ee("getMemberInformation",g.CONSTANTS.ScriptGangApiBaseRamCost/2);n("getMemberInformation",g.CONSTANTS.ScriptGangApiBaseRamCost/2),D.a(t,"getMemberInformation");try{for(const t of S.a.gang.members)if(t.name===e)return{agility:t.agi,agilityEquipMult:t.agi_mult,agilityAscensionMult:t.agi_asc_mult,augmentations:t.augmentations.slice(),charisma:t.cha,charismaEquipMult:t.cha_mult,charismaAscensionMult:t.cha_asc_mult,defense:t.def,defenseEquipMult:t.def_mult,defenseAscensionMult:t.def_asc_mult,dexterity:t.dex,dexterityEquipMult:t.dex_mult,dexterityAscensionMult:t.dex_asc_mult,equipment:t.upgrades.slice(),hacking:t.hack,hackingEquipMult:t.hack_mult,hackingAscensionMult:t.hack_asc_mult,strength:t.str,strengthEquipMult:t.str_mult,strengthAscensionMult:t.str_asc_mult,task:t.task};return t.log(`Invalid argument passed to gang.getMemberInformation(). No gang member could be found with name ${e}`),{}}catch(e){throw Object(j.d)(t,D.b("getMemberInformation",e))}},canRecruitMember:function(){if(t.checkingRam)return ee("canRecruitMember",g.CONSTANTS.ScriptGangApiBaseRamCost/4);n("canRecruitMember",g.CONSTANTS.ScriptGangApiBaseRamCost/4),D.a(t,"canRecruitMember");try{return S.a.gang.canRecruitMember()}catch(e){throw Object(j.d)(t,D.b("canRecruitMember",e))}},recruitMember:function(e){if(t.checkingRam)return ee("recruitMember",g.CONSTANTS.ScriptGangApiBaseRamCost/2);n("recruitMember",g.CONSTANTS.ScriptGangApiBaseRamCost/2),D.a(t,"recruitMember");try{const n=S.a.gang.recruitMember(e);return t.shouldLog("recruitMember")&&(n?t.log(`Successfully recruited Gang Member ${e}`):t.log(`Failed to recruit Gang Member ${e}`)),n}catch(e){throw Object(j.d)(t,D.b("recruitMember",e))}},getTaskNames:function(){if(t.checkingRam)return ee("getTaskNames",g.CONSTANTS.ScriptGangApiBaseRamCost/4);n("getTaskNames",g.CONSTANTS.ScriptGangApiBaseRamCost/4),D.a(t,"getTaskNames");try{const e=S.a.gang.getAllTaskNames();return e.unshift("Unassigned"),e}catch(e){throw Object(j.d)(t,D.b("getTaskNames",e))}},setMemberTask:function(e,i){if(t.checkingRam)return ee("setMemberTask",g.CONSTANTS.ScriptGangApiBaseRamCost/2);n("setMemberTask",g.CONSTANTS.ScriptGangApiBaseRamCost/2),D.a(t,"setMemberTask");try{for(const n of S.a.gang.members)if(n.name===e){const a=n.assignToTask(i);return t.shouldLog("setMemberTask")&&(a?t.log(`Successfully assigned Gang Member ${e} to ${i} task`):t.log(`Failed to assign Gang Member ${e} to ${i} task. ${e} is now Unassigned`)),a}return t.log(`Invalid argument passed to gang.setMemberTask(). No gang member could be found with name ${e}`),!1}catch(e){throw Object(j.d)(t,D.b("setMemberTask",e))}},getEquipmentNames:function(){if(t.checkingRam)return ee("getEquipmentNames",g.CONSTANTS.ScriptGangApiBaseRamCost/4);n("getEquipmentNames",g.CONSTANTS.ScriptGangApiBaseRamCost/4),D.a(t,"getEquipmentNames");try{return S.a.gang.getAllUpgradeNames()}catch(e){throw Object(j.d)(t,D.b("getEquipmentNames",e))}},getEquipmentCost:function(e){if(t.checkingRam)return ee("getEquipmentCost",g.CONSTANTS.ScriptGangApiBaseRamCost/2);n("getEquipmentCost",g.CONSTANTS.ScriptGangApiBaseRamCost/2),D.a(t,"getEquipmentCost");try{return S.a.gang.getUpgradeCost(e)}catch(e){throw Object(j.d)(t,D.b("getEquipmentCost",e))}},getEquipmentType:function(e){if(t.checkingRam)return ee("getEquipmentType",g.CONSTANTS.ScriptGangApiBaseRamCost/2);n("getEquipmentType",g.CONSTANTS.ScriptGangApiBaseRamCost/2),D.a(t,"getEquipmentType");try{return S.a.gang.getUpgradeType(e)}catch(e){throw Object(j.d)(t,D.b("getEquipmentType",e))}},purchaseEquipment:function(e,i){if(t.checkingRam)return ee("purchaseEquipment",g.CONSTANTS.ScriptGangApiBaseRamCost);n("purchaseEquipment",g.CONSTANTS.ScriptGangApiBaseRamCost),D.a(t,"purchaseEquipment");try{for(const n of S.a.gang.members)if(n.name===e){const a=n.buyUpgrade(i,S.a,S.a.gang);return t.shouldLog("purchaseEquipment")&&(a?t.log(`Purchased ${i} for Gang member ${e}`):t.log(`Failed to purchase ${i} for Gang member ${e}`)),a}return t.log(`Invalid argument passed to gang.purchaseEquipment(). No gang member could be found with name ${e}`),!1}catch(e){throw Object(j.d)(t,D.b("purchaseEquipment",e))}},ascendMember:function(e){if(t.checkingRam)return ee("ascendMember",g.CONSTANTS.ScriptGangApiBaseRamCost);n("ascendMember",g.CONSTANTS.ScriptGangApiBaseRamCost),D.a(t,"ascendMember");try{for(const n of S.a.gang.members)if(n.name===e)return S.a.gang.ascendMember(n,t);return t.log(`Invalid argument passed to gang.ascendMember(). No gang member could be found with name ${e}`),!1}catch(e){throw Object(j.d)(t,D.b("ascendMember",e))}},setTerritoryWarfare:function(e){if(t.checkingRam)return ee("setTerritoryWarfare",g.CONSTANTS.ScriptGangApiBaseRamCost/2);n("setTerritoryWarfare",g.CONSTANTS.ScriptGangApiBaseRamCost/2),D.a(t,"setTerritoryWarfare");try{e?(S.a.gang.territoryWarfareEngaged=!0,t.shouldLog("setTerritoryWarfare")&&t.log("Engaging in Gang Territory Warfare")):(S.a.gang.territoryWarfareEngaged=!1,t.shouldLog("setTerritoryWarfare")&&t.log("Disengaging in Gang Territory Warfare"))}catch(e){throw Object(j.d)(t,D.b("setTerritoryWarfare",e))}},getChanceToWinClash:function(e){if(t.checkingRam)return ee("getChanceToWinClash",g.CONSTANTS.ScriptGangApiBaseRamCost);n("getChanceToWinClash",g.CONSTANTS.ScriptGangApiBaseRamCost),D.a(t,"getChanceToWinClash");try{if(null==f.a[e])return t.log(`Invalid gang specified in gang.getChanceToWinClash() : ${e}`),0;const n=f.a[S.a.gang.facName].power;return n/(f.a[e].power+n)}catch(e){throw Object(j.d)(t,D.b("getChanceToWinClash",e))}},getBonusTime:function(){if(t.checkingRam)return 0;D.a(t,"getBonusTime");try{return Math.round(S.a.gang.storedCycles/5)}catch(e){throw Object(j.d)(t,D.b("getBonusTime",e))}}},bladeburner:{getContractNames:function(){if(t.checkingRam)return ee("getContractNames",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/10);if(n("getContractNames",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/10),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))return S.a.bladeburner.getContractNamesNetscriptFn();throw Object(j.d)(t,"getContractNames() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getOperationNames:function(){if(t.checkingRam)return ee("getOperationNames",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/10);if(n("getOperationNames",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/10),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))return S.a.bladeburner.getOperationNamesNetscriptFn();throw Object(j.d)(t,"getOperationNames() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getBlackOpNames:function(){if(t.checkingRam)return ee("getBlackOpNames",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/10);if(n("getBlackOpNames",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/10),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))return S.a.bladeburner.getBlackOpNamesNetscriptFn();throw Object(j.d)(t,"getBlackOpNames() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getGeneralActionNames:function(){if(t.checkingRam)return ee("getGeneralActionNames",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/10);if(n("getGeneralActionNames",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/10),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))return S.a.bladeburner.getGeneralActionNamesNetscriptFn();throw Object(j.d)(t,"getGeneralActionNames() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getSkillNames:function(){if(t.checkingRam)return ee("getSkillNames",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/10);if(n("getSkillNames",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/10),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))return S.a.bladeburner.getSkillNamesNetscriptFn();throw Object(j.d)(t,"getSkillNames() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},startAction:function(e="",i=""){if(t.checkingRam)return ee("startAction",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("startAction",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.startActionNetscriptFn(e,i,t)}catch(e){throw Object(j.d)(t,"Bladeburner.startAction() failed with exception: "+e)}throw Object(j.d)(t,"startAction() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},stopBladeburnerAction:function(){if(t.checkingRam)return ee("stopBladeburnerAction",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/2);if(n("stopBladeburnerAction",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/2),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))return S.a.bladeburner.resetAction();throw Object(j.d)(t,"stopBladeburnerAction() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getCurrentAction:function(){if(t.checkingRam)return ee("getCurrentAction",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/4);if(n("getCurrentAction",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost/4),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))return S.a.bladeburner.getTypeAndNameFromActionId(S.a.bladeburner.action);throw Object(j.d)(t,"getCurrentAction() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getActionTime:function(e="",i=""){if(t.checkingRam)return ee("getActionTime",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getActionTime",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.getActionTimeNetscriptFn(e,i,t)}catch(e){throw Object(j.d)(t,"Bladeburner.getActionTime() failed with exception: "+e)}throw Object(j.d)(t,"getActionTime() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getActionEstimatedSuccessChance:function(e="",i=""){if(t.checkingRam)return ee("getActionEstimatedSuccessChance",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getActionEstimatedSuccessChance",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.getActionEstimatedSuccessChanceNetscriptFn(e,i,t)}catch(e){throw Object(j.d)(t,"Bladeburner.getActionEstimatedSuccessChance() failed with exception: "+e)}throw Object(j.d)(t,"getActionEstimatedSuccessChance() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getActionRepGain:function(e="",i="",a){if(t.checkingRam)return ee("getActionRepGain",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);n("getActionRepGain",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),Object(L.a)(t,"getActionRepGain");try{var r=Object(L.b)("getActionAutolevel",e,i);const n=S.a.bladeburner.getActionIdFromTypeAndName(e,i);if(null==n)return t.log(r),-1;const s=S.a.bladeburner.getActionObject(n);return null==s?(t.log(r),-1):(o=null==a||isNaN(a)?Math.pow(s.rewardFac,s.level-1):Math.pow(s.rewardFac,a-1),s.rankGain*o*l.BitNodeMultipliers.BladeburnerRank);var o}catch(e){throw Object(j.d)(t,Object(L.c)("getActionAutolevel",e))}},getActionCountRemaining:function(e="",i=""){if(t.checkingRam)return ee("getActionCountRemaining",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getActionCountRemaining",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.getActionCountRemainingNetscriptFn(e,i,t)}catch(e){throw Object(j.d)(t,"Bladeburner.getActionCountRemaining() failed with exception: "+e)}throw Object(j.d)(t,"getActionCountRemaining() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getActionMaxLevel:function(e="",i=""){if(t.checkingRam)return ee("getActionMaxLevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);n("getActionMaxLevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),Object(L.a)(t,"getActionMaxLevel");try{var a=Object(L.b)("getActionMaxLevel",e,i);const n=S.a.bladeburner.getActionIdFromTypeAndName(e,i);if(null==n)return t.log(a),-1;const r=S.a.bladeburner.getActionObject(n);return null==r?(t.log(a),-1):r.maxLevel}catch(e){throw Object(j.d)(t,Object(L.c)("getActionMaxLevel",e))}},getActionCurrentLevel:function(e="",i=""){if(t.checkingRam)return ee("getActionCurrentLevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);n("getActionCurrentLevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),Object(L.a)(t,"getActionCurrentLevel");try{var a=Object(L.b)("getActionCurrentLevel",e,i);const n=S.a.bladeburner.getActionIdFromTypeAndName(e,i);if(null==n)return t.log(a),-1;const r=S.a.bladeburner.getActionObject(n);return null==r?(t.log(a),-1):r.level}catch(e){throw Object(j.d)(t,Object(L.c)("getActionCurrentLevel",e))}},getActionAutolevel:function(e="",i=""){if(t.checkingRam)return ee("getActionAutolevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);n("getActionAutolevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),Object(L.a)(t,"getActionAutolevel");try{var a=Object(L.b)("getActionAutolevel",e,i);const n=S.a.bladeburner.getActionIdFromTypeAndName(e,i);if(null==n)return t.log(a),!1;const r=S.a.bladeburner.getActionObject(n);return null==r?(t.log(a),!1):r.autoLevel}catch(e){throw Object(j.d)(t,Object(L.c)("getActionAutolevel",e))}},setActionAutolevel:function(e="",i="",a=!0){if(t.checkingRam)return ee("setActionAutolevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);n("setActionAutolevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),Object(L.a)(t,"setActionAutolevel");try{var r=Object(L.b)("setActionAutolevel",e,i);const n=S.a.bladeburner.getActionIdFromTypeAndName(e,i);if(null==n)return void t.log(r);const o=S.a.bladeburner.getActionObject(n);if(null==o)return void t.log(r);o.autoLevel=a}catch(e){throw Object(j.d)(t,Object(L.c)("setActionAutolevel",e))}},setActionLevel:function(e="",i="",a=1){if(t.checkingRam)return ee("setActionLevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);n("setActionLevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),Object(L.a)(t,"setActionLevel");try{var r=Object(L.b)("setActionLevel",e,i);const n=S.a.bladeburner.getActionIdFromTypeAndName(e,i);if(null==n)return void t.log(r);const o=S.a.bladeburner.getActionObject(n);if(null==o)return void t.log(r);if(a>o.maxLevel)return void t.log(`ERROR: bladeburner.${setActionLevel}() failed because level exceeds max level for given action.`);if(a<1)return void t.log(`ERROR: bladeburner.${setActionLevel}() failed because level is below 1.`);o.level=a}catch(e){throw Object(j.d)(t,Object(L.c)("setActionLevel",e))}},getRank:function(){if(t.checkingRam)return ee("getRank",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getRank",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))return S.a.bladeburner.rank;throw Object(j.d)(t,"getRank() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getSkillPoints:function(){if(t.checkingRam)return ee("getSkillPoints",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getSkillPoints",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))return S.a.bladeburner.skillPoints;throw Object(j.d)(t,"getSkillPoints() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getSkillLevel:function(e=""){if(t.checkingRam)return ee("getSkillLevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getSkillLevel",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.getSkillLevelNetscriptFn(e,t)}catch(e){throw Object(j.d)(t,"Bladeburner.getSkillLevel() failed with exception: "+e)}throw Object(j.d)(t,"getSkillLevel() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getSkillUpgradeCost:function(e=""){if(t.checkingRam)return ee("getSkillUpgradeCost",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getSkillUpgradeCost",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.getSkillUpgradeCostNetscriptFn(e,t)}catch(e){throw Object(j.d)(t,"Bladeburner.getSkillUpgradeCost() failed with exception: "+e)}throw Object(j.d)(t,"getSkillUpgradeCost() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},upgradeSkill:function(e){if(t.checkingRam)return ee("upgradeSkill",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("upgradeSkill",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.upgradeSkillNetscriptFn(e,t)}catch(e){throw Object(j.d)(t,"Bladeburner.upgradeSkill() failed with exception: "+e)}throw Object(j.d)(t,"upgradeSkill() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getTeamSize:function(e="",i=""){if(t.checkingRam)return ee("getTeamSize",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getTeamSize",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.getTeamSizeNetscriptFn(e,i,t)}catch(e){throw Object(j.d)(t,"Bladeburner.getTeamSize() failed with exception: "+e)}throw Object(j.d)(t,"getTeamSize() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},setTeamSize:function(e="",i="",a){if(t.checkingRam)return ee("setTeamSize",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("setTeamSize",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.setTeamSizeNetscriptFn(e,i,a,t)}catch(e){throw Object(j.d)(t,"Bladeburner.setTeamSize() failed with exception: "+e)}throw Object(j.d)(t,"setTeamSize() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getCityEstimatedPopulation:function(e){if(t.checkingRam)return ee("getCityEstimatedPopulation",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getCityEstimatedPopulation",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.getCityEstimatedPopulationNetscriptFn(e,t)}catch(e){throw Object(j.d)(t,"Bladeburner.getCityEstimatedPopulation() failed with exception: "+e)}throw Object(j.d)(t,"getCityEstimatedPopulation() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getCityEstimatedCommunities:function(e){if(t.checkingRam)return ee("getCityEstimatedCommunities",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getCityEstimatedCommunities",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.getCityEstimatedCommunitiesNetscriptFn(e,t)}catch(e){throw Object(j.d)(t,"Bladeburner.getCityEstimatedCommunities() failed with exception: "+e)}throw Object(j.d)(t,"getCityEstimatedCommunities() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getCityChaos:function(e){if(t.checkingRam)return ee("getCityChaos",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getCityChaos",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.getCityChaosNetscriptFn(e,t)}catch(e){throw Object(j.d)(t,"Bladeburner.getCityChaos() failed with exception: "+e)}throw Object(j.d)(t,"getCityChaos() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getCity:function(){if(t.checkingRam)return ee("getCity",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getCity",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.city}catch(e){throw Object(j.d)(t,"Bladeburner.getCity() failed with exception: "+e)}throw Object(j.d)(t,"getCity() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},switchCity:function(e){if(t.checkingRam)return ee("switchCity",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("switchCity",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))try{return S.a.bladeburner.switchCityNetscriptFn(e,t)}catch(e){throw Object(j.d)(t,"Bladeburner.switchCity() failed with exception: "+e)}throw Object(j.d)(t,"switchCity() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getStamina:function(){if(t.checkingRam)return ee("getStamina",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("getStamina",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))return[S.a.bladeburner.stamina,S.a.bladeburner.maxStamina];throw Object(j.d)(t,"getStamina() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},joinBladeburnerFaction:function(){if(t.checkingRam)return ee("joinBladeburnerFaction",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("joinBladeburnerFaction",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),S.a.bladeburner instanceof u.a&&(7===S.a.bitNodeN||ae))return S.a.bladeburner.joinBladeburnerFactionNetscriptFn(t);throw Object(j.d)(t,"joinBladeburnerFaction() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},joinBladeburnerDivision:function(){if(t.checkingRam)return ee("joinBladeburnerDivision",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost);if(n("joinBladeburnerDivision",g.CONSTANTS.ScriptBladeburnerApiBaseRamCost),7===S.a.bitNodeN||ae)return S.a.bladeburner instanceof u.a||(S.a.strength>=100&&S.a.defense>=100&&S.a.dexterity>=100&&S.a.agility>=100?(S.a.bladeburner=new u.a({new:!0}),t.log("You have been accepted into the Bladeburner division"),!0):(t.log("You do not meet the requirements for joining the Bladeburner division"),!1));throw Object(j.d)(t,"joinBladeburnerDivision() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")},getBonusTime:function(){if(t.checkingRam)return 0;if(7===S.a.bitNodeN||ae)return Math.round(S.a.bladeburner.storedCycles/5);throw Object(j.d)(t,"getBonusTime() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed at the Bladeburner division or because you do not have Source-File 7")}},codingcontract:{attempt:function(e,i,a=t.serverIp){if(t.checkingRam)return ee("attempt",g.CONSTANTS.ScriptCodingContractBaseRamCost);n("attempt",g.CONSTANTS.ScriptCodingContractBaseRamCost);const r=ue(i,a);if(null==r)return t.log(`ERROR: codingcontract.getData() failed because it could find the specified contract ${i} on server ${a}`),!1;if(Object(K.is2DArray)(e)){let t=[];for(let n=0;n=r.getMaxNumTries()?(t.log(`Coding Contract ${i} failed. Contract is now self-destructing`),o.removeContract(i)):t.log(`Coding Contract ${i} failed. ${r.getMaxNumTries()-r.tries} attempts remaining`),!1},getContractType:function(e,i=t.serverIp){if(t.checkingRam)return ee("getContractType",g.CONSTANTS.ScriptCodingContractBaseRamCost/2);n("getContractType",g.CONSTANTS.ScriptCodingContractBaseRamCost/2);let a=ue(e,i);return null==a?(t.log(`ERROR: codingcontract.getData() failed because it could find the specified contract ${e} on server ${i}`),null):a.getType()},getData:function(e,i=t.serverIp){if(t.checkingRam)return ee("getData",g.CONSTANTS.ScriptCodingContractBaseRamCost/2);n("getData",g.CONSTANTS.ScriptCodingContractBaseRamCost/2);let a=ue(e,i);if(null==a)return t.log(`ERROR: codingcontract.getData() failed because it could find the specified contract ${e} on server ${i}`),null;let r=a.getData();if(r.constructor===Array){const e=r.slice();for(let t=0;t0){if("unalias"===n[0])return n.join(" ");if(null!=(r=(t=n[0],a.hasOwnProperty(t)?a[t]:null)))n[0]=r;else null!=(r=u(n[0]))&&(n[0]=r);for(var i=0;i"+n.infoText+""})),i.push(Object(k.createElement)("p",{innerText:"---------------"}));var a=t.getFavorGain();2!=a.length&&(a=0),a=a[0],i.push(Object(k.createElement)("p",{innerText:"Reputation: "+Object(C.formatNumber)(t.playerReputation,4),tooltip:"You will earn "+Object(C.formatNumber)(a,0)+" faction favor upon resetting after installing an Augmentation"})),i.push(Object(k.createElement)("p",{innerText:"---------------"})),i.push(Object(k.createElement)("p",{innerText:"Faction Favor: "+Object(C.formatNumber)(t.favor,0),tooltip:"Faction favor increases the rate at which you earn reputation for this faction by 1% per favor. Faction favor is gained whenever you reset after installing an Augmentation. The amount of favor you gain depends on how much reputation you have with the faction"})),i.push(Object(k.createElement)("p",{innerText:"---------------"})),i.push(Object(k.createElement)("pre",{id:"faction-work-description-text",innerText:"Perform work/carry out assignments for your faction to help further its cause! By doing so you will earn reputation for your faction. You will also gain reputation passively over time, although at a very slow rate. Earning reputation will allow you to purchase Augmentations through this faction, which are powerful upgrades that enhance your abilities. Note that you cannot use your terminal or create scripts when you are performing a task!"})),i.push(Object(k.createElement)("br"));var r=Object(k.createElement)("div",{class:"faction-work-div"}),c=Object(k.createElement)("div",{class:"faction-work-div-wrapper"});r.appendChild(c),c.appendChild(Object(k.createElement)("a",{class:"a-link-button",innerText:"Hacking Mission",clickListener:()=>{l.Engine.loadMissionContent();var e=new p.a(t.playerReputation,t);return Object(p.d)(!0,e),e.init(),!1}})),c.appendChild(Object(k.createElement)("p",{innerText:"Attempt a hacking mission for your faction. A mission is a mini game that, if won, earns you significant reputation with this faction. (Recommended hacking level: 200+)"})),i.push(r);var d=Object(k.createElement)("div",{class:"faction-work-div"}),h=Object(k.createElement)("div",{class:"faction-work-div-wrapper"});d.appendChild(h),h.appendChild(Object(k.createElement)("a",{class:"std-button",innerText:"Hacking Contracts",clickListener:()=>(m.a.startFactionHackWork(t),!1)})),h.appendChild(Object(k.createElement)("p",{innerText:"Complete hacking contracts for your faction. Your effectiveness, which determines how much reputation you gain for this faction, is based on your hacking skill. You will gain hacking exp."})),i.push(d);var y=Object(k.createElement)("div",{class:"faction-work-div"}),E=Object(k.createElement)("div",{class:"faction-work-div-wrapper"});y.appendChild(E),E.appendChild(Object(k.createElement)("a",{class:"std-button",innerText:"Field Work",clickListener:()=>(m.a.startFactionFieldWork(t),!1)})),E.appendChild(Object(k.createElement)("p",{innerText:"Carry out field missions for your faction. Your effectiveness, which determines how much reputation you gain for this faction, is based on all of your stats. You will gain exp for all stats."})),i.push(y);var T=Object(k.createElement)("div",{class:"faction-work-div"}),S=Object(k.createElement)("div",{class:"faction-work-div-wrapper"});T.appendChild(S),S.appendChild(Object(k.createElement)("a",{class:"std-button",innerText:"Security Work",clickListener:()=>(m.a.startFactionSecurityWork(t),!1)})),S.appendChild(Object(k.createElement)("p",{innerText:"Serve in a security detail for your faction. Your effectiveness, which determines how much reputation you gain for this faction, is based on your combat stats. You will gain exp for all combat stats."})),i.push(T);var A=Object(k.createElement)("div",{class:"faction-work-div"}),w=Object(k.createElement)("div",{class:"faction-work-div-wrapper"});A.appendChild(w);var x=Object(k.createElement)("p",{innerText:"This donation will result in 0.000 reputation gain"}),R=Object(k.createElement)("input",{placeholder:"Donation amount",inputListener:()=>{let e=0;if(""!==R.value&&(e=parseFloat(R.value)),isNaN(e))x.innerText="Invalid donate amount entered!";else{var t=e/s.CONSTANTS.DonateMoneyToRepDivisor*m.a.faction_rep_mult;x.innerText="This donation will result in "+Object(C.formatNumber)(t,3)+" reputation gain"}}});w.appendChild(Object(k.createElement)("a",{class:"std-button",innerText:"Donate Money",clickListener:()=>{var n=parseFloat(R.value);if(isNaN(n)||n<0)Object(b.dialogBoxCreate)("Invalid amount entered!");else if(m.a.money.lt(n))Object(b.dialogBoxCreate)("You cannot afford to donate this much money!");else{m.a.loseMoney(n);var i=n/s.CONSTANTS.DonateMoneyToRepDivisor*m.a.faction_rep_mult;t.playerReputation+=i,Object(b.dialogBoxCreate)("You just donated "+f.numeralWrapper.format(n,"$0.000a")+" to "+t.name+" to gain "+Object(C.formatNumber)(i,3)+" reputation"),M(e)}}})),w.appendChild(R),w.appendChild(x),i.push(A);const N=Object(k.createElement)("div",{class:"faction-work-div",display:"inline"}),I=Object(k.createElement)("div",{class:"faction-work-div-wrapper"});if(N.appendChild(I),I.appendChild(Object(k.createElement)("a",{class:"std-button",innerText:"Purchase Augmentations",margin:"5px",clickListener:()=>(l.Engine.hideAllContent(),l.Engine.Display.factionAugmentationsContent.style.display="block",P(e),!1)})),I.appendChild(Object(k.createElement)("pre",{innerHTML:"
As your reputation with this faction rises, you will unlock Augmentations, which you can purchase to enhance your abilities.

"})),i.push(N),2!=m.a.bitNodeN||"Slum Snakes"!=e&&"Tetrads"!=e&&"The Syndicate"!=e&&"The Dark Army"!=e&&"Speakers for the Dead"!=e&&"NiteSec"!=e&&"The Black Hand"!=e){if("The Covenant"===e&&m.a.bitNodeN>=10&&g.SourceFileFlags[10]){const e=Object(k.createElement)("div",{class:"faction-work-div",display:"inline"}),t=Object(k.createElement)("div",{class:"faction-work-div-wrapper"});e.appendChild(t),t.appendChild(Object(k.createElement)("a",{class:"std-button",innerText:"Purchase Duplicate Sleeves",clickListener:()=>{Object(_.createPurchaseSleevesFromCovenantPopup)(m.a)}})),t.appendChild(Object(k.createElement)("p",{innerText:"Purchase Duplicate Sleeves. These are permanent! You can purchase up to 5 total."})),i.push(e)}A.style.display=t.favor>=Math.floor(s.CONSTANTS.BaseFavorToDonate*o.BitNodeMultipliers.RepToDonateToFaction)?"inline":"none",r.style.display=n.offerHackingMission?"inline":"none",d.style.display=n.offerHackingWork?"inline":"none",y.style.display=n.offerFieldWork?"inline":"none",T.style.display=n.offerSecurityWork?"inline":"none";for(B=0;B{if(m.a.inGang())l.Engine.loadGangContent();else{let i=!1;"NiteSec"!==e&&"The Black Hand"!==e||(i=!0);var t=Object(O.yesNoBoxGetYesButton)(),n=Object(O.yesNoBoxGetNoButton)();t.innerHTML="Create Gang",n.innerHTML="Cancel",t.addEventListener("click",()=>{m.a.startGang(e,i),document.getElementById("world-menu-header").click(),document.getElementById("world-menu-header").click(),l.Engine.loadGangContent(),Object(O.yesNoBoxClose)()}),n.addEventListener("click",()=>{Object(O.yesNoBoxClose)()});let a="";a=i?"This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare is not as important.

":"This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. Compared to hacking gangs, progression with combat gangs can be more difficult as territory management is more important. However, well-managed combat gangs can progress faster than hacking ones.

",Object(O.yesNoBoxCreate)(`Would you like to create a new Gang with ${e}?

`+"Note that this will prevent you from creating a Gang with any other Faction until this BitNode is destroyed.

"+a+"Other than hacking vs combat, there are NO differences between the Factions you can create a Gang with, and each of these Factions have all Augmentations available.")}}})),D.appendChild(Object(k.createElement)("p",{innerText:"Create and manage a gang for this Faction. Gangs will earn you money and faction reputation."})),i.splice(7,1,L),m.a.inGang()&&m.a.gang.facName!=e&&(L.style.display="none");for(var B=0;B(l.Engine.loadFactionContent(),M(e),!1)})),n.push(Object(k.createElement)("h1",{innerText:"Faction Augmentations"})),n.push(Object(k.createElement)("p",{id:"faction-augmentations-page-desc",innerHTML:"Lists all Augmentations that are available to purchase from "+e+"

Augmentations are powerful upgrades that will enhance your abilities."})),n.push(Object(k.createElement)("br")),n.push(Object(k.createElement)("br"));var a=Object(k.createElement)("ul");const r=Object(k.createElement)("a",{innerText:"Sort by Cost",class:"a-link-button",clickListener:()=>{h.Settings.PurchaseAugmentationsOrder=d.PurchaseAugmentationsOrderSetting.Cost;var e=t.augmentations.slice();e.sort((e,t)=>{var n=i.Augmentations[e],a=i.Augmentations[t];if(null==n||null==a)throw new Error("Invalid Augmentation Names");return n.baseCost-a.baseCost}),Object(v.removeChildrenFromElement)(a),A(a,e,t)}}),o=Object(k.createElement)("a",{innerText:"Sort by Reputation",class:"a-link-button",clickListener:()=>{h.Settings.PurchaseAugmentationsOrder=d.PurchaseAugmentationsOrderSetting.Reputation;var e=t.augmentations.slice();e.sort((e,t)=>{var n=i.Augmentations[e],a=i.Augmentations[t];if(null==n||null==a)throw new Error("Invalid Augmentation Names");return n.baseRepRequirement-a.baseRepRequirement}),Object(v.removeChildrenFromElement)(a),A(a,e,t)}}),s=Object(k.createElement)("a",{innerText:"Sort by Default Order",class:"a-link-button",clickListener:()=>{h.Settings.PurchaseAugmentationsOrder=d.PurchaseAugmentationsOrderSetting.Default,Object(v.removeChildrenFromElement)(a),A(a,t.augmentations,t)}});switch(n.push(r),n.push(o),n.push(s),h.Settings.PurchaseAugmentationsOrder){case d.PurchaseAugmentationsOrderSetting.Cost:r.click();break;case d.PurchaseAugmentationsOrderSetting.Reputation:o.click();break;default:s.click()}n.push(a);for(var c=0;c(h.Settings.SuppressBuyAugmentationConfirmation?R(s,n):w(s,n),!1)});s.name==r.AugmentationNames.NeuroFluxGovernor&&(g.innerText+=" - Level "+N());var _=Object(k.createElement)("p",{display:"inline"}),y=s.baseRepRequirement*a.augmentationRepRequirementMult;x(s)?s.name!=r.AugmentationNames.NeuroFluxGovernor&&(s.owned||l)?(g.setAttribute("class","a-link-button-inactive"),_.innerHTML="ALREADY OWNED"):n.playerReputation>=y?(g.setAttribute("class","a-link-button"),_.innerHTML="UNLOCKED - "+f.numeralWrapper.format(s.baseCost*a.augmentationPriceMult,"$0.000a")):(g.setAttribute("class","a-link-button-inactive"),_.innerHTML="LOCKED (Requires "+Object(C.formatNumber)(y,1)+" faction reputation) - "+f.numeralWrapper.format(s.baseCost*a.augmentationPriceMult,"$0.000a"),_.style.color="red"):(g.setAttribute("class","a-link-button-inactive"),_.innerHTML="LOCKED (Requires "+s.prereqs.join(",")+" as prerequisite(s))",_.style.color="red"),d.appendChild(g),p.appendChild(d),p.appendChild(_),u.appendChild(p),e.appendChild(u)}()}function w(e,t){const n=t.getInfo();var i=Object(O.yesNoBoxGetYesButton)(),a=Object(O.yesNoBoxGetNoButton)();i.innerHTML="Purchase",a.innerHTML="Cancel",i.addEventListener("click",function(){R(e,t)}),a.addEventListener("click",function(){Object(O.yesNoBoxClose)()}),Object(O.yesNoBoxCreate)("

"+e.name+"


"+e.info+"


Would you like to purchase the "+e.name+" Augmentation for $"+Object(C.formatNumber)(e.baseCost*n.augmentationPriceMult,2)+"?")}function x(e){var t=!0;if(e.prereqs&&e.prereqs.length>0)for(var n=0;n1?1:i<0?0:i}function o(e){null==e.baseDifficulty&&(e.baseDifficulty=e.hackDifficulty);var t=3;return(t+=e.baseDifficulty*a.a.hacking_exp_mult*.3)*i.BitNodeMultipliers.HackExpGain}function s(e){const t=(100-e.hackDifficulty)/100*((a.a.hacking_skill-(e.requiredHackingSkill-1))/a.a.hacking_skill)*a.a.hacking_money_mult/240;return t<0?0:t>1?1:t*i.BitNodeMultipliers.ScriptHackMoney}function l(e,t,n){const i=e.requiredHackingSkill*e.hackDifficulty;null==t&&(t=a.a.hacking_skill),null==n&&(n=a.a.intelligence);var r=2.5*i+500;return 5*(r/=t+50+.1*n)/a.a.hacking_speed_mult}function c(e,t,n){return 3.2*l(e,t,n)}function u(e,t,n){return 4*l(e,t,n)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const i=n(2);t.createOptionElement=function(e,t=""){let n=t;return""===n&&(n=e),i.createElement("option",{text:e,value:n})}},function(e,t,n){!function(e){"use strict";var t={3:"abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile",5:"class enum extends super const export import",6:"enum",strict:"implements interface let package private protected public static yield",strictBind:"eval arguments"},n="break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this",i={5:n,6:n+" const class extends export import super"},a="ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢴࢶ-ࢽऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡૹଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘ-ౚౠౡಀಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൔ-ൖൟ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᲀ-ᲈᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕ℘-ℝℤΩℨK-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞ々-〇〡-〩〱-〵〸-〼ぁ-ゖ゛-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿕ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꞮꞰ-ꞷꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꣽꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭥꭰ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ",r="‌‍·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-٩ٰۖ-ۜ۟-۪ۤۧۨ-ۭ۰-۹ܑܰ-݊ަ-ް߀-߉߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣ०-९ঁ-ঃ়া-ৄেৈো-্ৗৢৣ০-৯ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑ੦-ੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣ૦-૯ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣ୦-୯ஂா-ூெ-ைொ-்ௗ௦-௯ఀ-ఃా-ౄె-ైొ-్ౕౖౢౣ౦-౯ಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣ೦-೯ഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣ൦-൯ංඃ්ා-ුූෘ-ෟ෦-෯ෲෳัิ-ฺ็-๎๐-๙ັິ-ູົຼ່-ໍ໐-໙༘༙༠-༩༹༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှ၀-၉ၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏ-ႝ፝-፟፩-፱ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝០-៩᠋-᠍᠐-᠙ᢩᤠ-ᤫᤰ-᤻᥆-᥏᧐-᧚ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼-᪉᪐-᪙᪰-᪽ᬀ-ᬄ᬴-᭄᭐-᭙᭫-᭳ᮀ-ᮂᮡ-ᮭ᮰-᮹᯦-᯳ᰤ-᰷᱀-᱉᱐-᱙᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꘠-꘩꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣐-꣙꣠-꣱꤀-꤉ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀꧐-꧙ꧥ꧰-꧹ꨩ-ꨶꩃꩌꩍ꩐-꩙ꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭꯰-꯹ﬞ︀-️︠-︯︳︴﹍-﹏0-9_",o=new RegExp("["+a+"]"),s=new RegExp("["+a+r+"]");a=r=null;var l=[0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,17,26,6,37,11,29,3,35,5,7,2,4,43,157,19,35,5,35,5,39,9,51,157,310,10,21,11,7,153,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,66,18,2,1,11,21,11,25,71,55,7,1,65,0,16,3,2,2,2,26,45,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,56,50,14,50,785,52,76,44,33,24,27,35,42,34,4,0,13,47,15,3,22,0,2,0,36,17,2,24,85,6,2,0,2,3,2,14,2,9,8,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,19,0,13,4,159,52,19,3,54,47,21,1,2,0,185,46,42,3,37,47,21,0,60,42,86,25,391,63,32,0,449,56,264,8,2,36,18,0,50,29,881,921,103,110,18,195,2749,1070,4050,582,8634,568,8,30,114,29,19,47,17,3,32,20,6,18,881,68,12,0,67,12,65,0,32,6124,20,754,9486,1,3071,106,6,12,4,8,8,9,5991,84,2,70,2,1,3,0,3,1,3,3,2,11,2,0,2,6,2,64,2,3,3,7,2,6,2,27,2,3,2,4,2,0,4,6,2,339,3,24,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,7,4149,196,60,67,1213,3,2,26,2,1,2,0,3,0,2,9,2,3,2,0,2,0,7,0,5,0,2,0,2,0,2,2,2,1,2,0,3,0,2,0,2,0,2,0,2,0,2,1,2,0,3,3,2,6,2,3,2,3,2,0,2,9,2,16,6,2,2,4,2,16,4421,42710,42,4148,12,221,3,5761,10591,541],c=[509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,1306,2,54,14,32,9,16,3,46,10,54,9,7,2,37,13,2,9,52,0,13,2,49,13,10,2,4,9,83,11,7,0,161,11,6,9,7,3,57,0,2,6,3,1,3,2,10,0,11,1,3,6,4,4,193,17,10,9,87,19,13,9,214,6,3,8,28,1,83,16,16,9,82,12,9,9,84,14,5,9,423,9,838,7,2,7,17,9,57,21,2,13,19882,9,135,4,60,6,26,9,1016,45,17,3,19723,1,5319,4,4,5,9,7,3,6,31,3,149,2,1418,49,513,54,5,49,9,0,15,0,23,4,2,14,1361,6,2,16,3,6,2,1,2,4,2214,6,110,6,6,9,792487,239];function u(e,t){for(var n=65536,i=0;ie)return!1;if((n+=t[i+1])>=e)return!0}}function p(e,t){return e<65?36===e:e<91||(e<97?95===e:e<123||(e<=65535?e>=170&&o.test(String.fromCharCode(e)):!1!==t&&u(e,l)))}function m(e,t){return e<48?36===e:e<58||!(e<65)&&(e<91||(e<97?95===e:e<123||(e<=65535?e>=170&&s.test(String.fromCharCode(e)):!1!==t&&(u(e,l)||u(e,c)))))}var d=function(e,t){void 0===t&&(t={}),this.label=e,this.keyword=t.keyword,this.beforeExpr=!!t.beforeExpr,this.startsExpr=!!t.startsExpr,this.isLoop=!!t.isLoop,this.isAssign=!!t.isAssign,this.prefix=!!t.prefix,this.postfix=!!t.postfix,this.binop=t.binop||null,this.updateContext=null};function h(e,t){return new d(e,{beforeExpr:!0,binop:t})}var g={beforeExpr:!0},_={startsExpr:!0},y={};function f(e,t){return void 0===t&&(t={}),t.keyword=e,y[e]=new d(e,t)}var b={num:new d("num",_),regexp:new d("regexp",_),string:new d("string",_),name:new d("name",_),eof:new d("eof"),bracketL:new d("[",{beforeExpr:!0,startsExpr:!0}),bracketR:new d("]"),braceL:new d("{",{beforeExpr:!0,startsExpr:!0}),braceR:new d("}"),parenL:new d("(",{beforeExpr:!0,startsExpr:!0}),parenR:new d(")"),comma:new d(",",g),semi:new d(";",g),colon:new d(":",g),dot:new d("."),question:new d("?",g),arrow:new d("=>",g),template:new d("template"),ellipsis:new d("...",g),backQuote:new d("`",_),dollarBraceL:new d("${",{beforeExpr:!0,startsExpr:!0}),eq:new d("=",{beforeExpr:!0,isAssign:!0}),assign:new d("_=",{beforeExpr:!0,isAssign:!0}),incDec:new d("++/--",{prefix:!0,postfix:!0,startsExpr:!0}),prefix:new d("prefix",{beforeExpr:!0,prefix:!0,startsExpr:!0}),logicalOR:h("||",1),logicalAND:h("&&",2),bitwiseOR:h("|",3),bitwiseXOR:h("^",4),bitwiseAND:h("&",5),equality:h("==/!=",6),relational:h("",7),bitShift:h("<>",8),plusMin:new d("+/-",{beforeExpr:!0,binop:9,prefix:!0,startsExpr:!0}),modulo:h("%",10),star:h("*",10),slash:h("/",10),starstar:new d("**",{beforeExpr:!0}),_break:f("break"),_case:f("case",g),_catch:f("catch"),_continue:f("continue"),_debugger:f("debugger"),_default:f("default",g),_do:f("do",{isLoop:!0,beforeExpr:!0}),_else:f("else",g),_finally:f("finally"),_for:f("for",{isLoop:!0}),_function:f("function",_),_if:f("if"),_return:f("return",g),_switch:f("switch"),_throw:f("throw",g),_try:f("try"),_var:f("var"),_const:f("const"),_while:f("while",{isLoop:!0}),_with:f("with"),_new:f("new",{beforeExpr:!0,startsExpr:!0}),_this:f("this",_),_super:f("super",_),_class:f("class"),_extends:f("extends",g),_export:f("export"),_import:f("import"),_null:f("null",_),_true:f("true",_),_false:f("false",_),_in:f("in",{beforeExpr:!0,binop:7}),_instanceof:f("instanceof",{beforeExpr:!0,binop:7}),_typeof:f("typeof",{beforeExpr:!0,prefix:!0,startsExpr:!0}),_void:f("void",{beforeExpr:!0,prefix:!0,startsExpr:!0}),_delete:f("delete",{beforeExpr:!0,prefix:!0,startsExpr:!0})},E=/\r\n?|\n|\u2028|\u2029/,v=new RegExp(E.source,"g");function k(e){return 10===e||13===e||8232===e||8233===e}var C=/[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/,O=/(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g,T=Object.prototype,S=T.hasOwnProperty,M=T.toString;function P(e,t){return S.call(e,t)}var A=Array.isArray||function(e){return"[object Array]"===M.call(e)},w=function(e,t){this.line=e,this.column=t};w.prototype.offset=function(e){return new w(this.line,this.column+e)};var x=function(e,t,n){this.start=t,this.end=n,null!==e.sourceFile&&(this.source=e.sourceFile)};function R(e,t){for(var n=1,i=0;;){v.lastIndex=i;var a=v.exec(e);if(!(a&&a.index=2015&&(t.ecmaVersion-=2009),null==t.allowReserved&&(t.allowReserved=t.ecmaVersion<5),A(t.onToken)){var i=t.onToken;t.onToken=function(e){return i.push(e)}}return A(t.onComment)&&(t.onComment=function(e,t){return function(n,i,a,r,o,s){var l={type:n?"Block":"Line",value:i,start:a,end:r};e.locations&&(l.loc=new x(this,o,s)),e.ranges&&(l.range=[a,r]),t.push(l)}}(t,t.onComment)),t}var L={};function D(e){return new RegExp("^("+e.replace(/ /g,"|")+")$")}var B=function(e,n,a){this.options=e=I(e),this.sourceFile=e.sourceFile,this.keywords=D(i[e.ecmaVersion>=6?6:5]);var r="";if(!e.allowReserved){for(var o=e.ecmaVersion;!(r=t[o]);o--);"module"==e.sourceType&&(r+=" await")}this.reservedWords=D(r);var s=(r?r+" ":"")+t.strict;this.reservedWordsStrict=D(s),this.reservedWordsStrictBind=D(s+" "+t.strictBind),this.input=String(n),this.containsEsc=!1,this.loadPlugins(e.plugins),a?(this.pos=a,this.lineStart=this.input.lastIndexOf("\n",a-1)+1,this.curLine=this.input.slice(0,this.lineStart).split(E).length):(this.pos=this.lineStart=0,this.curLine=1),this.type=b.eof,this.value=null,this.start=this.end=this.pos,this.startLoc=this.endLoc=this.curPosition(),this.lastTokEndLoc=this.lastTokStartLoc=null,this.lastTokStart=this.lastTokEnd=this.pos,this.context=this.initialContext(),this.exprAllowed=!0,this.inModule="module"===e.sourceType,this.strict=this.inModule||this.strictDirective(this.pos),this.potentialArrowAt=-1,this.inFunction=this.inGenerator=this.inAsync=!1,this.yieldPos=this.awaitPos=0,this.labels=[],0===this.pos&&e.allowHashBang&&"#!"===this.input.slice(0,2)&&this.skipLineComment(2),this.scopeStack=[],this.enterFunctionScope()};B.prototype.isKeyword=function(e){return this.keywords.test(e)},B.prototype.isReservedWord=function(e){return this.reservedWords.test(e)},B.prototype.extend=function(e,t){this[e]=t(this[e])},B.prototype.loadPlugins=function(e){for(var t in e){var n=L[t];if(!n)throw new Error("Plugin '"+t+"' not found");n(this,e[t])}},B.prototype.parse=function(){var e=this.options.program||this.startNode();return this.nextToken(),this.parseTopLevel(e)};var j=B.prototype,W=/^(?:'((?:[^']|\.)*)'|"((?:[^"]|\.)*)"|;)/;j.strictDirective=function(e){for(;;){O.lastIndex=e,e+=O.exec(this.input)[0].length;var t=W.exec(this.input.slice(e));if(!t)return!1;if("use strict"==(t[1]||t[2]))return!0;e+=t[0].length}},j.eat=function(e){return this.type===e&&(this.next(),!0)},j.isContextual=function(e){return this.type===b.name&&this.value===e},j.eatContextual=function(e){return this.value===e&&this.eat(b.name)},j.expectContextual=function(e){this.eatContextual(e)||this.unexpected()},j.canInsertSemicolon=function(){return this.type===b.eof||this.type===b.braceR||E.test(this.input.slice(this.lastTokEnd,this.start))},j.insertSemicolon=function(){if(this.canInsertSemicolon())return this.options.onInsertedSemicolon&&this.options.onInsertedSemicolon(this.lastTokEnd,this.lastTokEndLoc),!0},j.semicolon=function(){this.eat(b.semi)||this.insertSemicolon()||this.unexpected()},j.afterTrailingComma=function(e,t){if(this.type==e)return this.options.onTrailingComma&&this.options.onTrailingComma(this.lastTokStart,this.lastTokStartLoc),t||this.next(),!0},j.expect=function(e){this.eat(e)||this.unexpected()},j.unexpected=function(e){this.raise(null!=e?e:this.start,"Unexpected token")};var F=function(){this.shorthandAssign=this.trailingComma=this.parenthesizedAssign=this.parenthesizedBind=-1};j.checkPatternErrors=function(e,t){if(e){e.trailingComma>-1&&this.raiseRecoverable(e.trailingComma,"Comma is not permitted after the rest element");var n=t?e.parenthesizedAssign:e.parenthesizedBind;n>-1&&this.raiseRecoverable(n,"Parenthesized pattern")}},j.checkExpressionErrors=function(e,t){var n=e?e.shorthandAssign:-1;if(!t)return n>=0;n>-1&&this.raise(n,"Shorthand property assignments are valid only in destructuring patterns")},j.checkYieldAwaitInDefaultParams=function(){this.yieldPos&&(!this.awaitPos||this.yieldPos=6&&(e.sourceType=this.options.sourceType),this.finishNode(e,"Program")};var H={kind:"loop"},G={kind:"switch"};U.isLet=function(){if(this.type!==b.name||this.options.ecmaVersion<6||"let"!=this.value)return!1;O.lastIndex=this.pos;var e=O.exec(this.input),t=this.pos+e[0].length,n=this.input.charCodeAt(t);if(91===n||123==n)return!0;if(p(n,!0)){for(var i=t+1;m(this.input.charCodeAt(i),!0);)++i;var a=this.input.slice(t,i);if(!this.isKeyword(a))return!0}return!1},U.isAsyncFunction=function(){if(this.type!==b.name||this.options.ecmaVersion<8||"async"!=this.value)return!1;O.lastIndex=this.pos;var e=O.exec(this.input),t=this.pos+e[0].length;return!(E.test(this.input.slice(this.pos,t))||"function"!==this.input.slice(t,t+8)||t+8!=this.input.length&&m(this.input.charAt(t+8)))},U.parseStatement=function(e,t,n){var i,a=this.type,r=this.startNode();switch(this.isLet()&&(a=b._var,i="let"),a){case b._break:case b._continue:return this.parseBreakContinueStatement(r,a.keyword);case b._debugger:return this.parseDebuggerStatement(r);case b._do:return this.parseDoStatement(r);case b._for:return this.parseForStatement(r);case b._function:return!e&&this.options.ecmaVersion>=6&&this.unexpected(),this.parseFunctionStatement(r,!1);case b._class:return e||this.unexpected(),this.parseClass(r,!0);case b._if:return this.parseIfStatement(r);case b._return:return this.parseReturnStatement(r);case b._switch:return this.parseSwitchStatement(r);case b._throw:return this.parseThrowStatement(r);case b._try:return this.parseTryStatement(r);case b._const:case b._var:return i=i||this.value,e||"var"==i||this.unexpected(),this.parseVarStatement(r,i);case b._while:return this.parseWhileStatement(r);case b._with:return this.parseWithStatement(r);case b.braceL:return this.parseBlock();case b.semi:return this.parseEmptyStatement(r);case b._export:case b._import:return this.options.allowImportExportEverywhere||(t||this.raise(this.start,"'import' and 'export' may only appear at the top level"),this.inModule||this.raise(this.start,"'import' and 'export' may appear only with 'sourceType: module'")),a===b._import?this.parseImport(r):this.parseExport(r,n);default:if(this.isAsyncFunction()&&e)return this.next(),this.parseFunctionStatement(r,!0);var o=this.value,s=this.parseExpression();return a===b.name&&"Identifier"===s.type&&this.eat(b.colon)?this.parseLabeledStatement(r,o,s):this.parseExpressionStatement(r,s)}},U.parseBreakContinueStatement=function(e,t){var n="break"==t;this.next(),this.eat(b.semi)||this.insertSemicolon()?e.label=null:this.type!==b.name?this.unexpected():(e.label=this.parseIdent(),this.semicolon());for(var i=0;i=6?this.eat(b.semi):this.semicolon(),this.finishNode(e,"DoWhileStatement")},U.parseForStatement=function(e){if(this.next(),this.labels.push(H),this.enterLexicalScope(),this.expect(b.parenL),this.type===b.semi)return this.parseFor(e,null);var t=this.isLet();if(this.type===b._var||this.type===b._const||t){var n=this.startNode(),i=t?"let":this.value;return this.next(),this.parseVar(n,!0,i),this.finishNode(n,"VariableDeclaration"),!(this.type===b._in||this.options.ecmaVersion>=6&&this.isContextual("of"))||1!==n.declarations.length||"var"!==i&&n.declarations[0].init?this.parseFor(e,n):this.parseForIn(e,n)}var a=new F,r=this.parseExpression(!0,a);return this.type===b._in||this.options.ecmaVersion>=6&&this.isContextual("of")?(this.toAssignable(r),this.checkLVal(r),this.checkPatternErrors(a,!0),this.parseForIn(e,r)):(this.checkExpressionErrors(a,!0),this.parseFor(e,r))},U.parseFunctionStatement=function(e,t){return this.next(),this.parseFunction(e,!0,!1,t)},U.isFunction=function(){return this.type===b._function||this.isAsyncFunction()},U.parseIfStatement=function(e){return this.next(),e.test=this.parseParenExpression(),e.consequent=this.parseStatement(!this.strict&&this.isFunction()),e.alternate=this.eat(b._else)?this.parseStatement(!this.strict&&this.isFunction()):null,this.finishNode(e,"IfStatement")},U.parseReturnStatement=function(e){return this.inFunction||this.options.allowReturnOutsideFunction||this.raise(this.start,"'return' outside of function"),this.next(),this.eat(b.semi)||this.insertSemicolon()?e.argument=null:(e.argument=this.parseExpression(),this.semicolon()),this.finishNode(e,"ReturnStatement")},U.parseSwitchStatement=function(e){var t;this.next(),e.discriminant=this.parseParenExpression(),e.cases=[],this.expect(b.braceL),this.labels.push(G),this.enterLexicalScope();for(var n=!1;this.type!=b.braceR;)if(this.type===b._case||this.type===b._default){var i=this.type===b._case;t&&this.finishNode(t,"SwitchCase"),e.cases.push(t=this.startNode()),t.consequent=[],this.next(),i?t.test=this.parseExpression():(n&&this.raiseRecoverable(this.lastTokStart,"Multiple default clauses"),n=!0,t.test=null),this.expect(b.colon)}else t||this.unexpected(),t.consequent.push(this.parseStatement(!0));return this.exitLexicalScope(),t&&this.finishNode(t,"SwitchCase"),this.next(),this.labels.pop(),this.finishNode(e,"SwitchStatement")},U.parseThrowStatement=function(e){return this.next(),E.test(this.input.slice(this.lastTokEnd,this.start))&&this.raise(this.lastTokEnd,"Illegal newline after throw"),e.argument=this.parseExpression(),this.semicolon(),this.finishNode(e,"ThrowStatement")};var K=[];U.parseTryStatement=function(e){if(this.next(),e.block=this.parseBlock(),e.handler=null,this.type===b._catch){var t=this.startNode();this.next(),this.expect(b.parenL),t.param=this.parseBindingAtom(),this.enterLexicalScope(),this.checkLVal(t.param,"let"),this.expect(b.parenR),t.body=this.parseBlock(!1),this.exitLexicalScope(),e.handler=this.finishNode(t,"CatchClause")}return e.finalizer=this.eat(b._finally)?this.parseBlock():null,e.handler||e.finalizer||this.raise(e.start,"Missing catch or finally clause"),this.finishNode(e,"TryStatement")},U.parseVarStatement=function(e,t){return this.next(),this.parseVar(e,!1,t),this.semicolon(),this.finishNode(e,"VariableDeclaration")},U.parseWhileStatement=function(e){return this.next(),e.test=this.parseParenExpression(),this.labels.push(H),e.body=this.parseStatement(!1),this.labels.pop(),this.finishNode(e,"WhileStatement")},U.parseWithStatement=function(e){return this.strict&&this.raise(this.start,"'with' in strict mode"),this.next(),e.object=this.parseParenExpression(),e.body=this.parseStatement(!1),this.finishNode(e,"WithStatement")},U.parseEmptyStatement=function(e){return this.next(),this.finishNode(e,"EmptyStatement")},U.parseLabeledStatement=function(e,t,n){for(var i=0;i=0;r--){var o=this.labels[r];if(o.statementStart!=e.start)break;o.statementStart=this.start,o.kind=a}return this.labels.push({name:t,kind:a,statementStart:this.start}),e.body=this.parseStatement(!0),("ClassDeclaration"==e.body.type||"VariableDeclaration"==e.body.type&&"var"!=e.body.kind||"FunctionDeclaration"==e.body.type&&(this.strict||e.body.generator))&&this.raiseRecoverable(e.body.start,"Invalid labeled declaration"),this.labels.pop(),e.label=n,this.finishNode(e,"LabeledStatement")},U.parseExpressionStatement=function(e,t){return e.expression=t,this.semicolon(),this.finishNode(e,"ExpressionStatement")},U.parseBlock=function(e){void 0===e&&(e=!0);var t=this.startNode();for(t.body=[],this.expect(b.braceL),e&&this.enterLexicalScope();!this.eat(b.braceR);){var n=this.parseStatement(!0);t.body.push(n)}return e&&this.exitLexicalScope(),this.finishNode(t,"BlockStatement")},U.parseFor=function(e,t){return e.init=t,this.expect(b.semi),e.test=this.type===b.semi?null:this.parseExpression(),this.expect(b.semi),e.update=this.type===b.parenR?null:this.parseExpression(),this.expect(b.parenR),this.exitLexicalScope(),e.body=this.parseStatement(!1),this.labels.pop(),this.finishNode(e,"ForStatement")},U.parseForIn=function(e,t){var n=this.type===b._in?"ForInStatement":"ForOfStatement";return this.next(),e.left=t,e.right=this.parseExpression(),this.expect(b.parenR),this.exitLexicalScope(),e.body=this.parseStatement(!1),this.labels.pop(),this.finishNode(e,n)},U.parseVar=function(e,t,n){for(e.declarations=[],e.kind=n;;){var i=this.startNode();if(this.parseVarId(i,n),this.eat(b.eq)?i.init=this.parseMaybeAssign(t):"const"!==n||this.type===b._in||this.options.ecmaVersion>=6&&this.isContextual("of")?"Identifier"==i.id.type||t&&(this.type===b._in||this.isContextual("of"))?i.init=null:this.raise(this.lastTokEnd,"Complex binding patterns require an initialization value"):this.unexpected(),e.declarations.push(this.finishNode(i,"VariableDeclarator")),!this.eat(b.comma))break}return e},U.parseVarId=function(e,t){e.id=this.parseBindingAtom(t),this.checkLVal(e.id,t,!1)},U.parseFunction=function(e,t,n,i){this.initFunction(e),this.options.ecmaVersion>=6&&!i&&(e.generator=this.eat(b.star)),this.options.ecmaVersion>=8&&(e.async=!!i),t&&(e.id="nullableID"===t&&this.type!=b.name?null:this.parseIdent(),e.id&&this.checkLVal(e.id,"var"));var a=this.inGenerator,r=this.inAsync,o=this.yieldPos,s=this.awaitPos,l=this.inFunction;return this.inGenerator=e.generator,this.inAsync=e.async,this.yieldPos=0,this.awaitPos=0,this.inFunction=!0,this.enterFunctionScope(),t||(e.id=this.type==b.name?this.parseIdent():null),this.parseFunctionParams(e),this.parseFunctionBody(e,n),this.inGenerator=a,this.inAsync=r,this.yieldPos=o,this.awaitPos=s,this.inFunction=l,this.finishNode(e,t?"FunctionDeclaration":"FunctionExpression")},U.parseFunctionParams=function(e){this.expect(b.parenL),e.params=this.parseBindingList(b.parenR,!1,this.options.ecmaVersion>=8,!0),this.checkYieldAwaitInDefaultParams()},U.parseClass=function(e,t){this.next(),this.parseClassId(e,t),this.parseClassSuper(e);var n=this.startNode(),i=!1;for(n.body=[],this.expect(b.braceL);!this.eat(b.braceR);)if(!this.eat(b.semi)){var a=this.startNode(),r=this.eat(b.star),o=!1,s=this.type===b.name&&"static"===this.value;this.parsePropertyName(a),a.static=s&&this.type!==b.parenL,a.static&&(r&&this.unexpected(),r=this.eat(b.star),this.parsePropertyName(a)),this.options.ecmaVersion>=8&&!r&&!a.computed&&"Identifier"===a.key.type&&"async"===a.key.name&&this.type!==b.parenL&&!this.canInsertSemicolon()&&(o=!0,this.parsePropertyName(a)),a.kind="method";var l=!1;if(!a.computed){var c=a.key;r||o||"Identifier"!==c.type||this.type===b.parenL||"get"!==c.name&&"set"!==c.name||(l=!0,a.kind=c.name,c=this.parsePropertyName(a)),!a.static&&("Identifier"===c.type&&"constructor"===c.name||"Literal"===c.type&&"constructor"===c.value)&&(i&&this.raise(c.start,"Duplicate constructor in the same class"),l&&this.raise(c.start,"Constructor can't have get/set modifier"),r&&this.raise(c.start,"Constructor can't be a generator"),o&&this.raise(c.start,"Constructor can't be an async method"),a.kind="constructor",i=!0)}if(this.parseClassMethod(n,a,r,o),l){var u="get"===a.kind?0:1;if(a.value.params.length!==u){var p=a.value.start;"get"===a.kind?this.raiseRecoverable(p,"getter should have no params"):this.raiseRecoverable(p,"setter should have exactly one param")}else"set"===a.kind&&"RestElement"===a.value.params[0].type&&this.raiseRecoverable(a.value.params[0].start,"Setter cannot use rest params")}}return e.body=this.finishNode(n,"ClassBody"),this.finishNode(e,t?"ClassDeclaration":"ClassExpression")},U.parseClassMethod=function(e,t,n,i){t.value=this.parseMethod(n,i),e.body.push(this.finishNode(t,"MethodDefinition"))},U.parseClassId=function(e,t){e.id=this.type===b.name?this.parseIdent():!0===t?this.unexpected():null},U.parseClassSuper=function(e){e.superClass=this.eat(b._extends)?this.parseExprSubscripts():null},U.parseExport=function(e,t){if(this.next(),this.eat(b.star))return this.expectContextual("from"),e.source=this.type===b.string?this.parseExprAtom():this.unexpected(),this.semicolon(),this.finishNode(e,"ExportAllDeclaration");if(this.eat(b._default)){var n;if(this.checkExport(t,"default",this.lastTokStart),this.type===b._function||(n=this.isAsyncFunction())){var i=this.startNode();this.next(),n&&this.next(),e.declaration=this.parseFunction(i,"nullableID",!1,n)}else if(this.type===b._class){var a=this.startNode();e.declaration=this.parseClass(a,"nullableID")}else e.declaration=this.parseMaybeAssign(),this.semicolon();return this.finishNode(e,"ExportDefaultDeclaration")}if(this.shouldParseExportStatement())e.declaration=this.parseStatement(!0),"VariableDeclaration"===e.declaration.type?this.checkVariableExport(t,e.declaration.declarations):this.checkExport(t,e.declaration.id.name,e.declaration.id.start),e.specifiers=[],e.source=null;else{if(e.declaration=null,e.specifiers=this.parseExportSpecifiers(t),this.eatContextual("from"))e.source=this.type===b.string?this.parseExprAtom():this.unexpected();else{for(var r=0;r=6&&e)switch(e.type){case"Identifier":this.inAsync&&"await"===e.name&&this.raise(e.start,"Can not use 'await' as identifier inside an async function");break;case"ObjectPattern":case"ArrayPattern":break;case"ObjectExpression":e.type="ObjectPattern";for(var n=0;n=6&&(e.computed||e.method||e.shorthand))){var n,i=e.key;switch(i.type){case"Identifier":n=i.name;break;case"Literal":n=String(i.value);break;default:return}var a=e.kind;if(this.options.ecmaVersion>=6)"__proto__"===n&&"init"===a&&(t.proto&&this.raiseRecoverable(i.start,"Redefinition of __proto__ property"),t.proto=!0);else{var r=t[n="$"+n];r?("init"===a?this.strict&&r.init||r.get||r.set:r.init||r[a])&&this.raiseRecoverable(i.start,"Redefinition of property"):r=t[n]={init:!1,get:!1,set:!1},r[a]=!0}}},$.parseExpression=function(e,t){var n=this.start,i=this.startLoc,a=this.parseMaybeAssign(e,t);if(this.type===b.comma){var r=this.startNodeAt(n,i);for(r.expressions=[a];this.eat(b.comma);)r.expressions.push(this.parseMaybeAssign(e,t));return this.finishNode(r,"SequenceExpression")}return a},$.parseMaybeAssign=function(e,t,n){if(this.inGenerator&&this.isContextual("yield"))return this.parseYield();var i=!1,a=-1,r=-1;t?(a=t.parenthesizedAssign,r=t.trailingComma,t.parenthesizedAssign=t.trailingComma=-1):(t=new F,i=!0);var o=this.start,s=this.startLoc;this.type!=b.parenL&&this.type!=b.name||(this.potentialArrowAt=this.start);var l=this.parseMaybeConditional(e,t);if(n&&(l=n.call(this,l,o,s)),this.type.isAssign){this.checkPatternErrors(t,!0),i||F.call(t);var c=this.startNodeAt(o,s);return c.operator=this.value,c.left=this.type===b.eq?this.toAssignable(l):l,t.shorthandAssign=-1,this.checkLVal(l),this.next(),c.right=this.parseMaybeAssign(e),this.finishNode(c,"AssignmentExpression")}return i&&this.checkExpressionErrors(t,!0),a>-1&&(t.parenthesizedAssign=a),r>-1&&(t.trailingComma=r),l},$.parseMaybeConditional=function(e,t){var n=this.start,i=this.startLoc,a=this.parseExprOps(e,t);if(this.checkExpressionErrors(t))return a;if(this.eat(b.question)){var r=this.startNodeAt(n,i);return r.test=a,r.consequent=this.parseMaybeAssign(),this.expect(b.colon),r.alternate=this.parseMaybeAssign(e),this.finishNode(r,"ConditionalExpression")}return a},$.parseExprOps=function(e,t){var n=this.start,i=this.startLoc,a=this.parseMaybeUnary(t,!1);return this.checkExpressionErrors(t)?a:a.start==n&&"ArrowFunctionExpression"===a.type?a:this.parseExprOp(a,n,i,-1,e)},$.parseExprOp=function(e,t,n,i,a){var r=this.type.binop;if(null!=r&&(!a||this.type!==b._in)&&r>i){var o=this.type===b.logicalOR||this.type===b.logicalAND,s=this.value;this.next();var l=this.start,c=this.startLoc,u=this.parseExprOp(this.parseMaybeUnary(null,!1),l,c,r,a),p=this.buildBinary(t,n,e,u,s,o);return this.parseExprOp(p,t,n,i,a)}return e},$.buildBinary=function(e,t,n,i,a,r){var o=this.startNodeAt(e,t);return o.left=n,o.operator=a,o.right=i,this.finishNode(o,r?"LogicalExpression":"BinaryExpression")},$.parseMaybeUnary=function(e,t){var n,i=this.start,a=this.startLoc;if(this.inAsync&&this.isContextual("await"))n=this.parseAwait(e),t=!0;else if(this.type.prefix){var r=this.startNode(),o=this.type===b.incDec;r.operator=this.value,r.prefix=!0,this.next(),r.argument=this.parseMaybeUnary(null,!0),this.checkExpressionErrors(e,!0),o?this.checkLVal(r.argument):this.strict&&"delete"===r.operator&&"Identifier"===r.argument.type?this.raiseRecoverable(r.start,"Deleting local variable in strict mode"):t=!0,n=this.finishNode(r,o?"UpdateExpression":"UnaryExpression")}else{if(n=this.parseExprSubscripts(e),this.checkExpressionErrors(e))return n;for(;this.type.postfix&&!this.canInsertSemicolon();){var s=this.startNodeAt(i,a);s.operator=this.value,s.prefix=!1,s.argument=n,this.checkLVal(n),this.next(),n=this.finishNode(s,"UpdateExpression")}}return!t&&this.eat(b.starstar)?this.buildBinary(i,a,n,this.parseMaybeUnary(null,!1),"**",!1):n},$.parseExprSubscripts=function(e){var t=this.start,n=this.startLoc,i=this.parseExprAtom(e),a="ArrowFunctionExpression"===i.type&&")"!==this.input.slice(this.lastTokStart,this.lastTokEnd);if(this.checkExpressionErrors(e)||a)return i;var r=this.parseSubscripts(i,t,n);return e&&"MemberExpression"===r.type&&(e.parenthesizedAssign>=r.start&&(e.parenthesizedAssign=-1),e.parenthesizedBind>=r.start&&(e.parenthesizedBind=-1)),r},$.parseSubscripts=function(e,t,n,i){for(var a,r=this.options.ecmaVersion>=8&&"Identifier"===e.type&&"async"===e.name&&this.lastTokEnd==e.end&&!this.canInsertSemicolon();;)if((a=this.eat(b.bracketL))||this.eat(b.dot)){var o=this.startNodeAt(t,n);o.object=e,o.property=a?this.parseExpression():this.parseIdent(!0),o.computed=!!a,a&&this.expect(b.bracketR),e=this.finishNode(o,"MemberExpression")}else if(!i&&this.eat(b.parenL)){var s=new F,l=this.yieldPos,c=this.awaitPos;this.yieldPos=0,this.awaitPos=0;var u=this.parseExprList(b.parenR,this.options.ecmaVersion>=8,!1,s);if(r&&!this.canInsertSemicolon()&&this.eat(b.arrow))return this.checkPatternErrors(s,!1),this.checkYieldAwaitInDefaultParams(),this.yieldPos=l,this.awaitPos=c,this.parseArrowExpression(this.startNodeAt(t,n),u,!0);this.checkExpressionErrors(s,!0),this.yieldPos=l||this.yieldPos,this.awaitPos=c||this.awaitPos;var p=this.startNodeAt(t,n);p.callee=e,p.arguments=u,e=this.finishNode(p,"CallExpression")}else{if(this.type!==b.backQuote)return e;var m=this.startNodeAt(t,n);m.tag=e,m.quasi=this.parseTemplate(),e=this.finishNode(m,"TaggedTemplateExpression")}},$.parseExprAtom=function(e){var t,n=this.potentialArrowAt==this.start;switch(this.type){case b._super:this.inFunction||this.raise(this.start,"'super' outside of function or class");case b._this:var i=this.type===b._this?"ThisExpression":"Super";return t=this.startNode(),this.next(),this.finishNode(t,i);case b.name:var a=this.start,r=this.startLoc,o=this.parseIdent(this.type!==b.name);if(this.options.ecmaVersion>=8&&"async"===o.name&&!this.canInsertSemicolon()&&this.eat(b._function))return this.parseFunction(this.startNodeAt(a,r),!1,!1,!0);if(n&&!this.canInsertSemicolon()){if(this.eat(b.arrow))return this.parseArrowExpression(this.startNodeAt(a,r),[o],!1);if(this.options.ecmaVersion>=8&&"async"===o.name&&this.type===b.name)return o=this.parseIdent(),!this.canInsertSemicolon()&&this.eat(b.arrow)||this.unexpected(),this.parseArrowExpression(this.startNodeAt(a,r),[o],!0)}return o;case b.regexp:var s=this.value;return(t=this.parseLiteral(s.value)).regex={pattern:s.pattern,flags:s.flags},t;case b.num:case b.string:return this.parseLiteral(this.value);case b._null:case b._true:case b._false:return(t=this.startNode()).value=this.type===b._null?null:this.type===b._true,t.raw=this.type.keyword,this.next(),this.finishNode(t,"Literal");case b.parenL:var l=this.start,c=this.parseParenAndDistinguishExpression(n);return e&&(e.parenthesizedAssign<0&&!this.isSimpleAssignTarget(c)&&(e.parenthesizedAssign=l),e.parenthesizedBind<0&&(e.parenthesizedBind=l)),c;case b.bracketL:return t=this.startNode(),this.next(),t.elements=this.parseExprList(b.bracketR,!0,!0,e),this.finishNode(t,"ArrayExpression");case b.braceL:return this.parseObj(!1,e);case b._function:return t=this.startNode(),this.next(),this.parseFunction(t,!1);case b._class:return this.parseClass(this.startNode(),!1);case b._new:return this.parseNew();case b.backQuote:return this.parseTemplate();default:this.unexpected()}},$.parseLiteral=function(e){var t=this.startNode();return t.value=e,t.raw=this.input.slice(this.start,this.end),this.next(),this.finishNode(t,"Literal")},$.parseParenExpression=function(){this.expect(b.parenL);var e=this.parseExpression();return this.expect(b.parenR),e},$.parseParenAndDistinguishExpression=function(e){var t,n=this.start,i=this.startLoc,a=this.options.ecmaVersion>=8;if(this.options.ecmaVersion>=6){this.next();var r,o,s=this.start,l=this.startLoc,c=[],u=!0,p=!1,m=new F,d=this.yieldPos,h=this.awaitPos;for(this.yieldPos=0,this.awaitPos=0;this.type!==b.parenR;){if(u?u=!1:this.expect(b.comma),a&&this.afterTrailingComma(b.parenR,!0)){p=!0;break}if(this.type===b.ellipsis){r=this.start,c.push(this.parseParenItem(this.parseRest())),this.type===b.comma&&this.raise(this.start,"Comma is not permitted after the rest element");break}this.type!==b.parenL||o||(o=this.start),c.push(this.parseMaybeAssign(!1,m,this.parseParenItem))}var g=this.start,_=this.startLoc;if(this.expect(b.parenR),e&&!this.canInsertSemicolon()&&this.eat(b.arrow))return this.checkPatternErrors(m,!1),this.checkYieldAwaitInDefaultParams(),o&&this.unexpected(o),this.yieldPos=d,this.awaitPos=h,this.parseParenArrowList(n,i,c);c.length&&!p||this.unexpected(this.lastTokStart),r&&this.unexpected(r),this.checkExpressionErrors(m,!0),this.yieldPos=d||this.yieldPos,this.awaitPos=h||this.awaitPos,c.length>1?((t=this.startNodeAt(s,l)).expressions=c,this.finishNodeAt(t,"SequenceExpression",g,_)):t=c[0]}else t=this.parseParenExpression();if(this.options.preserveParens){var y=this.startNodeAt(n,i);return y.expression=t,this.finishNode(y,"ParenthesizedExpression")}return t},$.parseParenItem=function(e){return e},$.parseParenArrowList=function(e,t,n){return this.parseArrowExpression(this.startNodeAt(e,t),n)};var Y=[];$.parseNew=function(){var e=this.startNode(),t=this.parseIdent(!0);if(this.options.ecmaVersion>=6&&this.eat(b.dot))return e.meta=t,e.property=this.parseIdent(!0),"target"!==e.property.name&&this.raiseRecoverable(e.property.start,"The only valid meta property for new is new.target"),this.inFunction||this.raiseRecoverable(e.start,"new.target can only be used in functions"),this.finishNode(e,"MetaProperty");var n=this.start,i=this.startLoc;return e.callee=this.parseSubscripts(this.parseExprAtom(),n,i,!0),this.eat(b.parenL)?e.arguments=this.parseExprList(b.parenR,this.options.ecmaVersion>=8,!1):e.arguments=Y,this.finishNode(e,"NewExpression")},$.parseTemplateElement=function(){var e=this.startNode();return e.value={raw:this.input.slice(this.start,this.end).replace(/\r\n?/g,"\n"),cooked:this.value},this.next(),e.tail=this.type===b.backQuote,this.finishNode(e,"TemplateElement")},$.parseTemplate=function(){var e=this.startNode();this.next(),e.expressions=[];var t=this.parseTemplateElement();for(e.quasis=[t];!t.tail;)this.expect(b.dollarBraceL),e.expressions.push(this.parseExpression()),this.expect(b.braceR),e.quasis.push(t=this.parseTemplateElement());return this.next(),this.finishNode(e,"TemplateLiteral")},$.parseObj=function(e,t){var n=this.startNode(),i=!0,a={};for(n.properties=[],this.next();!this.eat(b.braceR);){if(i)i=!1;else if(this.expect(b.comma),this.afterTrailingComma(b.braceR))break;var r,o,s,l,c=this.startNode();this.options.ecmaVersion>=6&&(c.method=!1,c.shorthand=!1,(e||t)&&(s=this.start,l=this.startLoc),e||(r=this.eat(b.star))),this.parsePropertyName(c),e||!(this.options.ecmaVersion>=8)||r||c.computed||"Identifier"!==c.key.type||"async"!==c.key.name||this.type===b.parenL||this.type===b.colon||this.canInsertSemicolon()?o=!1:(o=!0,this.parsePropertyName(c,t)),this.parsePropertyValue(c,e,r,o,s,l,t),this.checkPropClash(c,a),n.properties.push(this.finishNode(c,"Property"))}return this.finishNode(n,e?"ObjectPattern":"ObjectExpression")},$.parsePropertyValue=function(e,t,n,i,a,r,o){if((n||i)&&this.type===b.colon&&this.unexpected(),this.eat(b.colon))e.value=t?this.parseMaybeDefault(this.start,this.startLoc):this.parseMaybeAssign(!1,o),e.kind="init";else if(this.options.ecmaVersion>=6&&this.type===b.parenL)t&&this.unexpected(),e.kind="init",e.method=!0,e.value=this.parseMethod(n,i);else if(this.options.ecmaVersion>=5&&!e.computed&&"Identifier"===e.key.type&&("get"===e.key.name||"set"===e.key.name)&&this.type!=b.comma&&this.type!=b.braceR){(n||i||t)&&this.unexpected(),e.kind=e.key.name,this.parsePropertyName(e),e.value=this.parseMethod(!1);var s="get"===e.kind?0:1;if(e.value.params.length!==s){var l=e.value.start;"get"===e.kind?this.raiseRecoverable(l,"getter should have no params"):this.raiseRecoverable(l,"setter should have exactly one param")}else"set"===e.kind&&"RestElement"===e.value.params[0].type&&this.raiseRecoverable(e.value.params[0].start,"Setter cannot use rest params")}else this.options.ecmaVersion>=6&&!e.computed&&"Identifier"===e.key.type?((this.keywords.test(e.key.name)||(this.strict?this.reservedWordsStrict:this.reservedWords).test(e.key.name)||this.inGenerator&&"yield"==e.key.name||this.inAsync&&"await"==e.key.name)&&this.raiseRecoverable(e.key.start,"'"+e.key.name+"' can not be used as shorthand property"),e.kind="init",t?e.value=this.parseMaybeDefault(a,r,e.key):this.type===b.eq&&o?(o.shorthandAssign<0&&(o.shorthandAssign=this.start),e.value=this.parseMaybeDefault(a,r,e.key)):e.value=e.key,e.shorthand=!0):this.unexpected()},$.parsePropertyName=function(e){if(this.options.ecmaVersion>=6){if(this.eat(b.bracketL))return e.computed=!0,e.key=this.parseMaybeAssign(),this.expect(b.bracketR),e.key;e.computed=!1}return e.key=this.type===b.num||this.type===b.string?this.parseExprAtom():this.parseIdent(!0)},$.initFunction=function(e){e.id=null,this.options.ecmaVersion>=6&&(e.generator=!1,e.expression=!1),this.options.ecmaVersion>=8&&(e.async=!1)},$.parseMethod=function(e,t){var n=this.startNode(),i=this.inGenerator,a=this.inAsync,r=this.yieldPos,o=this.awaitPos,s=this.inFunction;return this.initFunction(n),this.options.ecmaVersion>=6&&(n.generator=e),this.options.ecmaVersion>=8&&(n.async=!!t),this.inGenerator=n.generator,this.inAsync=n.async,this.yieldPos=0,this.awaitPos=0,this.inFunction=!0,this.enterFunctionScope(),this.expect(b.parenL),n.params=this.parseBindingList(b.parenR,!1,this.options.ecmaVersion>=8),this.checkYieldAwaitInDefaultParams(),this.parseFunctionBody(n,!1),this.inGenerator=i,this.inAsync=a,this.yieldPos=r,this.awaitPos=o,this.inFunction=s,this.finishNode(n,"FunctionExpression")},$.parseArrowExpression=function(e,t,n){var i=this.inGenerator,a=this.inAsync,r=this.yieldPos,o=this.awaitPos,s=this.inFunction;return this.enterFunctionScope(),this.initFunction(e),this.options.ecmaVersion>=8&&(e.async=!!n),this.inGenerator=!1,this.inAsync=e.async,this.yieldPos=0,this.awaitPos=0,this.inFunction=!0,e.params=this.toAssignableList(t,!0),this.parseFunctionBody(e,!0),this.inGenerator=i,this.inAsync=a,this.yieldPos=r,this.awaitPos=o,this.inFunction=s,this.finishNode(e,"ArrowFunctionExpression")},$.parseFunctionBody=function(e,t){var n=t&&this.type!==b.braceL,i=this.strict,a=!1;if(n)e.body=this.parseMaybeAssign(),e.expression=!0,this.checkParams(e,!1);else{var r=this.options.ecmaVersion>=7&&!this.isSimpleParamList(e.params);i&&!r||(a=this.strictDirective(this.end))&&r&&this.raiseRecoverable(e.start,"Illegal 'use strict' directive in function with non-simple parameter list");var o=this.labels;this.labels=[],a&&(this.strict=!0),this.checkParams(e,!i&&!a&&!t&&this.isSimpleParamList(e.params)),e.body=this.parseBlock(!1),e.expression=!1,this.labels=o}this.exitFunctionScope(),this.strict&&e.id&&this.checkLVal(e.id,"none"),this.strict=i},$.isSimpleParamList=function(e){for(var t=0;t=6||-1==this.input.slice(this.start,this.end).indexOf("\\"))&&this.raiseRecoverable(this.start,"The keyword '"+this.value+"' is reserved"),this.inGenerator&&"yield"===this.value&&this.raiseRecoverable(this.start,"Can not use 'yield' as identifier inside a generator"),this.inAsync&&"await"===this.value&&this.raiseRecoverable(this.start,"Can not use 'await' as identifier inside an async function"),t.name=this.value):e&&this.type.keyword?t.name=this.type.keyword:this.unexpected(),this.next(),this.finishNode(t,"Identifier")},$.parseYield=function(){this.yieldPos||(this.yieldPos=this.start);var e=this.startNode();return this.next(),this.type==b.semi||this.canInsertSemicolon()||this.type!=b.star&&!this.type.startsExpr?(e.delegate=!1,e.argument=null):(e.delegate=this.eat(b.star),e.argument=this.parseMaybeAssign()),this.finishNode(e,"YieldExpression")},$.parseAwait=function(){this.awaitPos||(this.awaitPos=this.start);var e=this.startNode();return this.next(),e.argument=this.parseMaybeUnary(null,!0),this.finishNode(e,"AwaitExpression")};var z=B.prototype;z.raise=function(e,t){var n=R(this.input,e);t+=" ("+n.line+":"+n.column+")";var i=new SyntaxError(t);throw i.pos=e,i.loc=n,i.raisedAt=this.pos,i},z.raiseRecoverable=z.raise,z.curPosition=function(){if(this.options.locations)return new w(this.curLine,this.pos-this.lineStart)};var V=B.prototype,J=Object.assign||function(e){for(var t=[],n=arguments.length-1;n-- >0;)t[n]=arguments[n+1];for(var i=0;i=0;e--)if(this.context[e].generator)return!0;return!1},ne.updateContext=function(e){var t,n=this.type;n.keyword&&e==b.dot?this.exprAllowed=!1:(t=n.updateContext)?t.call(this,e):this.exprAllowed=n.beforeExpr},b.parenR.updateContext=b.braceR.updateContext=function(){if(1!=this.context.length){var e,t=this.context.pop();t===te.b_stat&&(e=this.curContext())&&"function"===e.token?(this.context.pop(),this.exprAllowed=!1):this.exprAllowed=t===te.b_tmpl||!t.isExpr}else this.exprAllowed=!0},b.braceL.updateContext=function(e){this.context.push(this.braceIsBlock(e)?te.b_stat:te.b_expr),this.exprAllowed=!0},b.dollarBraceL.updateContext=function(){this.context.push(te.b_tmpl),this.exprAllowed=!0},b.parenL.updateContext=function(e){var t=e===b._if||e===b._for||e===b._with||e===b._while;this.context.push(t?te.p_stat:te.p_expr),this.exprAllowed=!0},b.incDec.updateContext=function(){},b._function.updateContext=function(e){e.beforeExpr&&e!==b.semi&&e!==b._else&&(e!==b.colon&&e!==b.braceL||this.curContext()!==te.b_stat)&&this.context.push(te.f_expr),this.exprAllowed=!1},b.backQuote.updateContext=function(){this.curContext()===te.q_tmpl?this.context.pop():this.context.push(te.q_tmpl),this.exprAllowed=!1},b.star.updateContext=function(e){e==b._function&&(this.curContext()===te.f_expr?this.context[this.context.length-1]=te.f_expr_gen:this.context.push(te.f_gen)),this.exprAllowed=!0},b.name.updateContext=function(e){var t=!1;this.options.ecmaVersion>=6&&("of"==this.value&&!this.exprAllowed||"yield"==this.value&&this.inGeneratorContext())&&(t=!0),this.exprAllowed=t};var ie=function(e){this.type=e.type,this.value=e.value,this.start=e.start,this.end=e.end,e.options.locations&&(this.loc=new x(e,e.startLoc,e.endLoc)),e.options.ranges&&(this.range=[e.start,e.end])},ae=B.prototype,re="object"==typeof Packages&&"[object JavaPackage]"==Object.prototype.toString.call(Packages);function oe(e,t,n,i){try{return new RegExp(e,t)}catch(e){if(void 0!==n)throw e instanceof SyntaxError&&i.raise(n,"Error parsing regular expression: "+e.message),e}}ae.next=function(){this.options.onToken&&this.options.onToken(new ie(this)),this.lastTokEnd=this.end,this.lastTokStart=this.start,this.lastTokEndLoc=this.endLoc,this.lastTokStartLoc=this.startLoc,this.nextToken()},ae.getToken=function(){return this.next(),new ie(this)},"undefined"!=typeof Symbol&&(ae[Symbol.iterator]=function(){var e=this;return{next:function(){var t=e.getToken();return{done:t.type===b.eof,value:t}}}}),ae.curContext=function(){return this.context[this.context.length-1]},ae.nextToken=function(){var e=this.curContext();return e&&e.preserveSpace||this.skipSpace(),this.start=this.pos,this.options.locations&&(this.startLoc=this.curPosition()),this.pos>=this.input.length?this.finishToken(b.eof):e.override?e.override(this):void this.readToken(this.fullCharCodeAtPos())},ae.readToken=function(e){return p(e,this.options.ecmaVersion>=6)||92===e?this.readWord():this.getTokenFromCode(e)},ae.fullCharCodeAtPos=function(){var e=this.input.charCodeAt(this.pos);if(e<=55295||e>=57344)return e;var t=this.input.charCodeAt(this.pos+1);return(e<<10)+t-56613888},ae.skipBlockComment=function(){var e,t=this.options.onComment&&this.curPosition(),n=this.pos,i=this.input.indexOf("*/",this.pos+=2);if(-1===i&&this.raise(this.pos-2,"Unterminated comment"),this.pos=i+2,this.options.locations)for(v.lastIndex=n;(e=v.exec(this.input))&&e.index8&&e<14||e>=5760&&C.test(String.fromCharCode(e))))break e;++this.pos}}},ae.finishToken=function(e,t){this.end=this.pos,this.options.locations&&(this.endLoc=this.curPosition());var n=this.type;this.type=e,this.value=t,this.updateContext(n)},ae.readToken_dot=function(){var e=this.input.charCodeAt(this.pos+1);if(e>=48&&e<=57)return this.readNumber(!0);var t=this.input.charCodeAt(this.pos+2);return this.options.ecmaVersion>=6&&46===e&&46===t?(this.pos+=3,this.finishToken(b.ellipsis)):(++this.pos,this.finishToken(b.dot))},ae.readToken_slash=function(){var e=this.input.charCodeAt(this.pos+1);return this.exprAllowed?(++this.pos,this.readRegexp()):61===e?this.finishOp(b.assign,2):this.finishOp(b.slash,1)},ae.readToken_mult_modulo_exp=function(e){var t=this.input.charCodeAt(this.pos+1),n=1,i=42===e?b.star:b.modulo;return this.options.ecmaVersion>=7&&42===t&&(++n,i=b.starstar,t=this.input.charCodeAt(this.pos+2)),61===t?this.finishOp(b.assign,n+1):this.finishOp(i,n)},ae.readToken_pipe_amp=function(e){var t=this.input.charCodeAt(this.pos+1);return t===e?this.finishOp(124===e?b.logicalOR:b.logicalAND,2):61===t?this.finishOp(b.assign,2):this.finishOp(124===e?b.bitwiseOR:b.bitwiseAND,1)},ae.readToken_caret=function(){var e=this.input.charCodeAt(this.pos+1);return 61===e?this.finishOp(b.assign,2):this.finishOp(b.bitwiseXOR,1)},ae.readToken_plus_min=function(e){var t=this.input.charCodeAt(this.pos+1);return t===e?45==t&&62==this.input.charCodeAt(this.pos+2)&&E.test(this.input.slice(this.lastTokEnd,this.pos))?(this.skipLineComment(3),this.skipSpace(),this.nextToken()):this.finishOp(b.incDec,2):61===t?this.finishOp(b.assign,2):this.finishOp(b.plusMin,1)},ae.readToken_lt_gt=function(e){var t=this.input.charCodeAt(this.pos+1),n=1;return t===e?(n=62===e&&62===this.input.charCodeAt(this.pos+2)?3:2,61===this.input.charCodeAt(this.pos+n)?this.finishOp(b.assign,n+1):this.finishOp(b.bitShift,n)):33==t&&60==e&&45==this.input.charCodeAt(this.pos+2)&&45==this.input.charCodeAt(this.pos+3)?(this.inModule&&this.unexpected(),this.skipLineComment(4),this.skipSpace(),this.nextToken()):(61===t&&(n=2),this.finishOp(b.relational,n))},ae.readToken_eq_excl=function(e){var t=this.input.charCodeAt(this.pos+1);return 61===t?this.finishOp(b.equality,61===this.input.charCodeAt(this.pos+2)?3:2):61===e&&62===t&&this.options.ecmaVersion>=6?(this.pos+=2,this.finishToken(b.arrow)):this.finishOp(61===e?b.eq:b.prefix,1)},ae.getTokenFromCode=function(e){switch(e){case 46:return this.readToken_dot();case 40:return++this.pos,this.finishToken(b.parenL);case 41:return++this.pos,this.finishToken(b.parenR);case 59:return++this.pos,this.finishToken(b.semi);case 44:return++this.pos,this.finishToken(b.comma);case 91:return++this.pos,this.finishToken(b.bracketL);case 93:return++this.pos,this.finishToken(b.bracketR);case 123:return++this.pos,this.finishToken(b.braceL);case 125:return++this.pos,this.finishToken(b.braceR);case 58:return++this.pos,this.finishToken(b.colon);case 63:return++this.pos,this.finishToken(b.question);case 96:if(this.options.ecmaVersion<6)break;return++this.pos,this.finishToken(b.backQuote);case 48:var t=this.input.charCodeAt(this.pos+1);if(120===t||88===t)return this.readRadixNumber(16);if(this.options.ecmaVersion>=6){if(111===t||79===t)return this.readRadixNumber(8);if(98===t||66===t)return this.readRadixNumber(2)}case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return this.readNumber(!1);case 34:case 39:return this.readString(e);case 47:return this.readToken_slash();case 37:case 42:return this.readToken_mult_modulo_exp(e);case 124:case 38:return this.readToken_pipe_amp(e);case 94:return this.readToken_caret();case 43:case 45:return this.readToken_plus_min(e);case 60:case 62:return this.readToken_lt_gt(e);case 61:case 33:return this.readToken_eq_excl(e);case 126:return this.finishOp(b.prefix,1)}this.raise(this.pos,"Unexpected character '"+le(e)+"'")},ae.finishOp=function(e,t){var n=this.input.slice(this.pos,this.pos+t);return this.pos+=t,this.finishToken(e,n)};var se=!!oe("￿","u");function le(e){return e<=65535?String.fromCharCode(e):(e-=65536,String.fromCharCode(55296+(e>>10),56320+(1023&e)))}ae.readRegexp=function(){for(var e,t,n=this,i=this.pos;;){n.pos>=n.input.length&&n.raise(i,"Unterminated regular expression");var a=n.input.charAt(n.pos);if(E.test(a)&&n.raise(i,"Unterminated regular expression"),e)e=!1;else{if("["===a)t=!0;else if("]"===a&&t)t=!1;else if("/"===a&&!t)break;e="\\"===a}++n.pos}var r=this.input.slice(i,this.pos);++this.pos;var o=this.readWord1(),s=r,l="";if(o){var c=/^[gim]*$/;this.options.ecmaVersion>=6&&(c=/^[gimuy]*$/),c.test(o)||this.raise(i,"Invalid regular expression flag"),o.indexOf("u")>=0&&(se?l="u":(s=(s=s.replace(/\\u\{([0-9a-fA-F]+)\}/g,function(e,t,a){return(t=Number("0x"+t))>1114111&&n.raise(i+a+3,"Code point out of bounds"),"x"})).replace(/\\u([a-fA-F0-9]{4})|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,"x"),l=l.replace("u","")))}var u=null;return re||(oe(s,l,i,this),u=oe(r,o)),this.finishToken(b.regexp,{pattern:r,flags:o,value:u})},ae.readInt=function(e,t){for(var n=this.pos,i=0,a=0,r=null==t?1/0:t;a=97?s-97+10:s>=65?s-65+10:s>=48&&s<=57?s-48:1/0)>=e)break;++this.pos,i=i*e+o}return this.pos===n||null!=t&&this.pos-n!==t?null:i},ae.readRadixNumber=function(e){this.pos+=2;var t=this.readInt(e);return null==t&&this.raise(this.start+2,"Expected number in radix "+e),p(this.fullCharCodeAtPos())&&this.raise(this.pos,"Identifier directly after number"),this.finishToken(b.num,t)},ae.readNumber=function(e){var t=this.pos,n=!1,i=48===this.input.charCodeAt(this.pos);e||null!==this.readInt(10)||this.raise(t,"Invalid number"),i&&this.pos==t+1&&(i=!1);var a=this.input.charCodeAt(this.pos);46!==a||i||(++this.pos,this.readInt(10),n=!0,a=this.input.charCodeAt(this.pos)),69!==a&&101!==a||i||(43!==(a=this.input.charCodeAt(++this.pos))&&45!==a||++this.pos,null===this.readInt(10)&&this.raise(t,"Invalid number"),n=!0),p(this.fullCharCodeAtPos())&&this.raise(this.pos,"Identifier directly after number");var r,o=this.input.slice(t,this.pos);return n?r=parseFloat(o):i&&1!==o.length?/[89]/.test(o)||this.strict?this.raise(t,"Invalid number"):r=parseInt(o,8):r=parseInt(o,10),this.finishToken(b.num,r)},ae.readCodePoint=function(){var e,t=this.input.charCodeAt(this.pos);if(123===t){this.options.ecmaVersion<6&&this.unexpected();var n=++this.pos;e=this.readHexChar(this.input.indexOf("}",this.pos)-this.pos),++this.pos,e>1114111&&this.raise(n,"Code point out of bounds")}else e=this.readHexChar(4);return e},ae.readString=function(e){for(var t="",n=++this.pos;;){this.pos>=this.input.length&&this.raise(this.start,"Unterminated string constant");var i=this.input.charCodeAt(this.pos);if(i===e)break;92===i?(t+=this.input.slice(n,this.pos),t+=this.readEscapedChar(!1),n=this.pos):(k(i)&&this.raise(this.start,"Unterminated string constant"),++this.pos)}return t+=this.input.slice(n,this.pos++),this.finishToken(b.string,t)},ae.readTmplToken=function(){for(var e="",t=this.pos;;){this.pos>=this.input.length&&this.raise(this.start,"Unterminated template");var n=this.input.charCodeAt(this.pos);if(96===n||36===n&&123===this.input.charCodeAt(this.pos+1))return this.pos===this.start&&this.type===b.template?36===n?(this.pos+=2,this.finishToken(b.dollarBraceL)):(++this.pos,this.finishToken(b.backQuote)):(e+=this.input.slice(t,this.pos),this.finishToken(b.template,e));if(92===n)e+=this.input.slice(t,this.pos),e+=this.readEscapedChar(!0),t=this.pos;else if(k(n)){switch(e+=this.input.slice(t,this.pos),++this.pos,n){case 13:10===this.input.charCodeAt(this.pos)&&++this.pos;case 10:e+="\n";break;default:e+=String.fromCharCode(n)}this.options.locations&&(++this.curLine,this.lineStart=this.pos),t=this.pos}else++this.pos}},ae.readEscapedChar=function(e){var t=this.input.charCodeAt(++this.pos);switch(++this.pos,t){case 110:return"\n";case 114:return"\r";case 120:return String.fromCharCode(this.readHexChar(2));case 117:return le(this.readCodePoint());case 116:return"\t";case 98:return"\b";case 118:return"\v";case 102:return"\f";case 13:10===this.input.charCodeAt(this.pos)&&++this.pos;case 10:return this.options.locations&&(this.lineStart=this.pos,++this.curLine),"";default:if(t>=48&&t<=55){var n=this.input.substr(this.pos-1,3).match(/^[0-7]+/)[0],i=parseInt(n,8);return i>255&&(n=n.slice(0,-1),i=parseInt(n,8)),"0"!==n&&(this.strict||e)&&this.raise(this.pos-2,"Octal literal in strict mode"),this.pos+=n.length-1,String.fromCharCode(i)}return String.fromCharCode(t)}},ae.readHexChar=function(e){var t=this.pos,n=this.readInt(16,e);return null===n&&this.raise(t,"Bad character escape sequence"),n},ae.readWord1=function(){this.containsEsc=!1;for(var e="",t=!0,n=this.pos,i=this.options.ecmaVersion>=6;this.posd()?1/0:t*a.CONSTANTS.BaseCostFor1GBOfRamServer*i.BitNodeMultipliers.PurchasedServerCost}function m(){return Math.round(a.CONSTANTS.PurchasedServerLimit*i.BitNodeMultipliers.PurchasedServerLimit)}function d(){const e=Math.round(a.CONSTANTS.PurchasedServerMaxRam*i.BitNodeMultipliers.PurchasedServerMaxRam);return 1<<31-Math.clz32(e)}function h(e){const t=p(e);if(r.a.money.lt(t))Object(s.dialogBoxCreate)("You don't have enough money to purchase this server!");else if(r.a.purchasedServers.length>=m())Object(s.dialogBoxCreate)("You have reached the maximum limit of "+m()+" servers. You cannot purchase any more. You can delete some of your purchased servers using the deleteServer() Netscript function in a script");else{var n=Object(c.yesNoTxtInpBoxGetInput)();if(""!=n){var i=new o.d({ip:Object(l.a)(),hostname:n,organizationName:"",isConnectedTo:!1,adminRights:!0,purchasedByPlayer:!0,maxRam:e});Object(o.a)(i),r.a.purchasedServers.push(i.ip);var a=r.a.getHomeComputer();a.serversOnNetwork.push(i.ip),i.serversOnNetwork.push(a.ip),r.a.loseMoney(t),Object(s.dialogBoxCreate)("Server successfully purchased with hostname "+n)}else Object(s.dialogBoxCreate)("You must enter a hostname for your new server!")}}function g(e){if(r.a.money.lt(e))return void Object(s.dialogBoxCreate)("You do not have enough money to purchase additional RAM for your home computer");const t=r.a.getHomeComputer();t.maxRam>=a.CONSTANTS.HomeComputerMaxRam?Object(s.dialogBoxCreate)("You cannot upgrade your home computer RAM because it is at its maximum possible value"):(t.maxRam*=2,r.a.loseMoney(e),Object(s.dialogBoxCreate)("Purchased additional RAM for home computer! It now has "+t.maxRam+"GB of RAM."))}},function(e,t,n){"use strict";(function(e){n.d(t,"a",function(){return O}),n.d(t,"d",function(){return T}),n.d(t,"c",function(){return S}),n.d(t,"b",function(){return M});var i=n(164),a=n(163),r=n(10),o=n(63),s=n(15),l=n(51),c=n(3),u=n(9),p=n(14),m=n(29),d=n(90),h=n(2),g=n(37),_=n(12),y=n(4),f=n(40),b=n(16),E=n(27),v=n(67),k=n(23);e(document).keydown(function(e){if(_.routing.isOn(_.Page.Gang)&&e.altKey){if(null!=j.gangMemberFilter&&j.gangMemberFilter===document.activeElement)return;e.keyCode===m.KEY[1]?"block"===j.gangTerritorySubpage.style.display&&j.managementButton.click():e.keyCode===m.KEY[2]&&"block"===j.gangManagementSubpage.style.display&&j.territoryButton.click()}}),e(document).mousedown(function(t){j.gangMemberUpgradeBoxOpened&&null==e(t.target).closest("#gang-member-upgrade-popup-box-content").get(0)&&(Object(v.removeElement)(j.gangMemberUpgradeBox),j.gangMemberUpgradeBox=null,j.gangMemberUpgradeBoxContent=null,j.gangMemberUpgradeBoxOpened=!1,j.gangMemberUpgradeBoxElements=null)});let C=["Slum Snakes","Tetrads","The Syndicate","The Dark Army","Speakers for the Dead","NiteSec","The Black Hand"],O={"Slum Snakes":{power:1,territory:1/7},Tetrads:{power:1,territory:1/7},"The Syndicate":{power:1,territory:1/7},"The Dark Army":{power:1,territory:1/7},"Speakers for the Dead":{power:1,territory:1/7},NiteSec:{power:1,territory:1/7},"The Black Hand":{power:1,territory:1/7}};function T(){O={"Slum Snakes":{power:1,territory:1/7},Tetrads:{power:1,territory:1/7},"The Syndicate":{power:1,territory:1/7},"The Dark Army":{power:1,territory:1/7},"Speakers for the Dead":{power:1,territory:1/7},NiteSec:{power:1,territory:1/7},"The Black Hand":{power:1,territory:1/7}}}function S(e){O=JSON.parse(e,p.Reviver)}function M(e,t=!1){this.facName=e,this.members=[],this.wanted=1,this.respect=1,this.isHackingGang=t,this.respectGainRate=0,this.wantedGainRate=0,this.moneyGainRate=0,this.storedCycles=0,this.storedTerritoryAndPowerCycles=0,this.territoryClashChance=0,this.territoryWarfareEngaged=!1,this.notifyMemberDeath=!0}function P(e){this.name=e,this.task="Unassigned",this.earnedRespect=0,this.hack=1,this.str=1,this.def=1,this.dex=1,this.agi=1,this.cha=1,this.hack_exp=0,this.str_exp=0,this.def_exp=0,this.dex_exp=0,this.agi_exp=0,this.cha_exp=0,this.hack_mult=1,this.str_mult=1,this.def_mult=1,this.dex_mult=1,this.agi_mult=1,this.cha_mult=1,this.hack_asc_mult=1,this.str_asc_mult=1,this.def_asc_mult=1,this.dex_asc_mult=1,this.agi_asc_mult=1,this.cha_asc_mult=1,this.upgrades=[],this.augmentations=[]}function A(e="",t="",n=!1,i=!1,a={baseRespect:0,baseWanted:0,baseMoney:0,hackWeight:0,strWeight:0,defWeight:0,dexWeight:0,agiWeight:0,chaWeight:0,difficulty:0}){this.name=e,this.desc=t,this.isHacking=n,this.isCombat=i,this.baseRespect=a.baseRespect?a.baseRespect:0,this.baseWanted=a.baseWanted?a.baseWanted:0,this.baseMoney=a.baseMoney?a.baseMoney:0,this.hackWeight=a.hackWeight?a.hackWeight:0,this.strWeight=a.strWeight?a.strWeight:0,this.defWeight=a.defWeight?a.defWeight:0,this.dexWeight=a.dexWeight?a.dexWeight:0,this.agiWeight=a.agiWeight?a.agiWeight:0,this.chaWeight=a.chaWeight?a.chaWeight:0,100!=Math.round(this.hackWeight+this.strWeight+this.defWeight+this.dexWeight+this.agiWeight+this.chaWeight)&&console.error(`GangMemberTask ${this.name} weights do not add up to 100`),this.difficulty=a.difficulty?a.difficulty:1,this.territory=a.territory?a.territory:{money:1,respect:1,wanted:1}}M.prototype.getPower=function(){return O[this.facName].power},M.prototype.getTerritory=function(){return O[this.facName].territory},M.prototype.process=function(e=1,t){const n=1e3/r.Engine._idleSpeed;if(isNaN(e)&&console.error(`NaN passed into Gang.process(): ${e}`),this.storedCycles+=e,this.storedCycles<2*n)return;const i=Math.min(this.storedCycles,5*n);try{this.processGains(i,t),this.processExperienceGains(i),this.processTerritoryAndPowerGains(i),this.storedCycles-=i}catch(e){Object(f.exceptionAlert)(`Exception caught when processing Gang: ${e}`)}},M.prototype.processGains=function(e=1,t){for(var n=0,i=0,a=0,r=0;rt&&(n=1),this.wanted=n,this.wanted<1&&(this.wanted=1)}else console.warn("ERROR: wantedLevelGains is NaN");"number"==typeof n?(t.gainMoney(n*e),t.recordMoneySource(n*e,"gang")):console.warn("ERROR: respectGains is NaN")},M.prototype.processTerritoryAndPowerGains=function(e=1){if(this.storedTerritoryAndPowerCycles+=e,!(this.storedTerritoryAndPowerCycles<100)){this.storedTerritoryAndPowerCycles-=100;var t=this.facName;for(const e in O)if(O.hasOwnProperty(e))if(e==t)O[e].power+=this.calculatePower();else{const t=Math.random();if(t<.5){const t=.005*O[e].power;O[e].power+=Math.min(.85,t)}else{const n=.75*t*O[e].territory;O[e].power+=n}}this.territoryWarfareEngaged?this.territoryClashChance=1:this.territoryClashChance>0&&(this.territoryClashChance=Math.max(0,this.territoryClashChance-.01));for(var n=0;ne!==n),i=Object(b.getRandomInt)(0,e.length-1),a=C[n],r=e[i];if(!(a!==t&&r!==t||Math.random()=30)&&this.respect>=this.getRespectNeededToRecruitMember()},M.prototype.getRespectNeededToRecruitMember=function(){if(this.members.length<3)return 0;const e=this.members.length-2;return Math.round(.9*Math.pow(e,3)+Math.pow(e,2))},M.prototype.recruitMember=function(e){if(""===(e=String(e))||!this.canRecruitMember())return!1;if(this.members.filter(t=>t.name===e).length>=1)return!1;let t=new P(e);return this.members.push(t),_.routing.isOn(_.Page.Gang)&&(this.createGangMemberDisplayElement(t),this.updateGangContent()),!0},M.prototype.getWantedPenalty=function(){return this.respect/(this.respect+this.wanted)},M.prototype.processExperienceGains=function(e=1){for(var t=0;t=0;--e){const n=this.members[e];if("Territory Warfare"!==n.task)continue;const i=t/Math.pow(n.def,.6);Math.random()")):t.log(`Ascended Gang member ${e.name}`),_.routing.isOn(_.Page.Gang)&&this.displayGangMemberList(),n}catch(e){if(null!=t)throw e;Object(f.exceptionAlert)(e)}},M.prototype.getDiscount=function(){const e=this.getPower(),t=this.respect,n=Math.pow(t,.01)+t/5e6+Math.pow(e,.01)+e/1e6-1;return Math.max(1,n)},M.prototype.getAllTaskNames=function(){let e=[];const t=Object.keys(w);return e=this.isHackingGang?t.filter(e=>{let t=w[e];return null!=t&&("Unassigned"!==e&&t.isHacking)}):t.filter(e=>{let t=w[e];return null!=t&&("Unassigned"!==e&&t.isCombat)})},M.prototype.getAllUpgradeNames=function(){return Object.keys(B)},M.prototype.getUpgradeCost=function(e){return null==B[e]?1/0:B[e].getCost(this)},M.prototype.getUpgradeType=function(e){const t=B[e];if(null==t)return"";switch(t.type){case"w":return"Weapon";case"a":return"Armor";case"v":return"Vehicle";case"r":return"Rootkit";case"g":return"Augmentation";default:return""}},M.prototype.toJSON=function(){return Object(p.Generic_toJSON)("Gang",this)},M.fromJSON=function(e){return Object(p.Generic_fromJSON)(M,e.data)},p.Reviver.constructors.Gang=M,P.prototype.calculateSkill=function(e,t=1){return Math.max(Math.floor(t*(32*Math.log(e+534.5)-200)),1)},P.prototype.updateSkillLevels=function(){this.hack=this.calculateSkill(this.hack_exp,this.hack_mult*this.hack_asc_mult),this.str=this.calculateSkill(this.str_exp,this.str_mult*this.str_asc_mult),this.def=this.calculateSkill(this.def_exp,this.def_mult*this.def_asc_mult),this.dex=this.calculateSkill(this.dex_exp,this.dex_mult*this.dex_asc_mult),this.agi=this.calculateSkill(this.agi_exp,this.agi_mult*this.agi_asc_mult),this.cha=this.calculateSkill(this.cha_exp,this.cha_mult*this.cha_asc_mult)},P.prototype.calculatePower=function(){return(this.hack+this.str+this.def+this.dex+this.agi+this.cha)/95},P.prototype.assignToTask=function(e){return w.hasOwnProperty(e)?(this.task=e,!0):(this.task="Unassigned",!1)},P.prototype.unassignFromTask=function(){this.task="Unassigned"},P.prototype.getTask=function(){return this.task instanceof A&&(this.task=this.task.name),w.hasOwnProperty(this.task)?w[this.task]:w.Unassigned},P.prototype.calculateRespectGain=function(e){const t=this.getTask();if(null==t||!(t instanceof A)||0===t.baseRespect)return 0;var n=t.hackWeight/100*this.hack+t.strWeight/100*this.str+t.defWeight/100*this.def+t.dexWeight/100*this.dex+t.agiWeight/100*this.agi+t.chaWeight/100*this.cha;if((n-=4*t.difficulty)<=0)return 0;const i=Math.pow(100*O[e.facName].territory,t.territory.respect)/100;if(isNaN(i)||i<=0)return 0;var a=e.getWantedPenalty();return 11*t.baseRespect*n*i*a},P.prototype.calculateWantedLevelGain=function(e){const t=this.getTask();if(null==t||!(t instanceof A)||0===t.baseWanted)return 0;var n=t.hackWeight/100*this.hack+t.strWeight/100*this.str+t.defWeight/100*this.def+t.dexWeight/100*this.dex+t.agiWeight/100*this.agi+t.chaWeight/100*this.cha;if((n-=3.5*t.difficulty)<=0)return 0;const i=Math.pow(100*O[e.facName].territory,t.territory.wanted)/100;return isNaN(i)||i<=0?0:t.baseWanted<0?.5*t.baseWanted*n*i:7*t.baseWanted/Math.pow(3*n*i,.8)},P.prototype.calculateMoneyGain=function(e){const t=this.getTask();if(null==t||!(t instanceof A)||0===t.baseMoney)return 0;var n=t.hackWeight/100*this.hack+t.strWeight/100*this.str+t.defWeight/100*this.def+t.dexWeight/100*this.dex+t.agiWeight/100*this.agi+t.chaWeight/100*this.cha;if((n-=3.2*t.difficulty)<=0)return 0;const i=Math.pow(100*O[e.facName].territory,t.territory.money)/100;if(isNaN(i)||i<=0)return 0;var a=e.getWantedPenalty();return 5*t.baseMoney*n*i*a},P.prototype.gainExperience=function(e=1){const t=this.getTask();if(null==t||!(t instanceof A)||t===w.Unassigned)return;const n=Math.pow(t.difficulty,.9)*e;this.hack_exp+=t.hackWeight/1500*n,this.str_exp+=t.strWeight/1500*n,this.def_exp+=t.defWeight/1500*n,this.dex_exp+=t.dexWeight/1500*n,this.agi_exp+=t.agiWeight/1500*n,this.cha_exp+=t.chaWeight/1500*n},P.prototype.recordEarnedRespect=function(e=1,t){this.earnedRespect+=this.calculateRespectGain(t)*e},P.prototype.ascend=function(){const e=this.getAscensionResults(),t=e.hack,n=e.str,i=e.def,a=e.dex,r=e.agi,o=e.cha;this.hack_asc_mult+=t,this.str_asc_mult+=n,this.def_asc_mult+=i,this.dex_asc_mult+=a,this.agi_asc_mult+=r,this.cha_asc_mult+=o,this.upgrades.length=0,this.hack_mult=1,this.str_mult=1,this.def_mult=1,this.dex_mult=1,this.agi_mult=1,this.cha_mult=1;for(let e=0;e{x=e.name,R=e.desc,N=e.isHacking,I=e.isCombat,L=e.params,w[x]=new A(x,R,N,I,L)}),D.prototype.getCost=function(e){const t=e.getDiscount();return this.cost/t},D.prototype.createDescription=function(){const e=["Increases:"];null!=this.mults.str&&e.push(`* Strength by ${Math.round(100*(this.mults.str-1))}%`),null!=this.mults.def&&e.push(`* Defense by ${Math.round(100*(this.mults.def-1))}%`),null!=this.mults.dex&&e.push(`* Dexterity by ${Math.round(100*(this.mults.dex-1))}%`),null!=this.mults.agi&&e.push(`* Agility by ${Math.round(100*(this.mults.agi-1))}%`),null!=this.mults.cha&&e.push(`* Charisma by ${Math.round(100*(this.mults.cha-1))}%`),null!=this.mults.hack&&e.push(`* Hacking by ${Math.round(100*(this.mults.hack-1))}%`),this.desc=e.join("
")},D.prototype.apply=function(e){null!=this.mults.str&&(e.str_mult*=this.mults.str),null!=this.mults.def&&(e.def_mult*=this.mults.def),null!=this.mults.dex&&(e.dex_mult*=this.mults.dex),null!=this.mults.agi&&(e.agi_mult*=this.mults.agi),null!=this.mults.cha&&(e.cha_mult*=this.mults.cha),null!=this.mults.hack&&(e.hack_mult*=this.mults.hack)},D.prototype.toJSON=function(){return Object(p.Generic_toJSON)("GangMemberUpgrade",this)},D.fromJSON=function(e){return Object(p.Generic_fromJSON)(D,e.data)},p.Reviver.constructors.GangMemberUpgrade=D;const B={};a.gangMemberUpgradesMetadata.forEach(e=>{!function(e,t,n,i){B[e]=new D(e,t,n,i)}(e.name,e.cost,e.upgType,e.mults)}),M.prototype.createGangMemberUpgradeBox=function(e,t=""){const n="gang-member-upgrade-popup-box";if(j.gangMemberUpgradeBoxOpened){if(null==j.gangMemberUpgradeBoxElements||null==j.gangMemberUpgradeBox||null==j.gangMemberUpgradeBoxContent)return void console.error("Refreshing Gang member upgrade box throws error because required elements are null");for(var i=2;i-1||this.members[i].task.indexOf(a)>-1){var r=this.members[i].createGangMemberUpgradePanel(this,e);j.gangMemberUpgradeBoxContent.appendChild(r),j.gangMemberUpgradeBoxElements.push(r)}}else{j.gangMemberUpgradeBoxFilter=Object(h.createElement)("input",{type:"text",placeholder:"Filter gang members",value:t,onkeyup:()=>{var t=j.gangMemberUpgradeBoxFilter.value.toString();this.createGangMemberUpgradeBox(e,t)}}),j.gangMemberUpgradeBoxDiscount=Object(h.createElement)("p",{innerText:"Discount: -"+c.numeralWrapper.format(1-1/this.getDiscount(),"0.00%"),marginLeft:"6px",tooltip:"You get a discount on equipment and upgrades based on your gang's respect and power. More respect and power leads to more discounts."}),j.gangMemberUpgradeBoxElements=[j.gangMemberUpgradeBoxFilter,j.gangMemberUpgradeBoxDiscount];for(a=j.gangMemberUpgradeBoxFilter.value.toString(),i=0;i-1||this.members[i].task.indexOf(a)>-1)&&j.gangMemberUpgradeBoxElements.push(this.members[i].createGangMemberUpgradePanel(this,e));j.gangMemberUpgradeBox=Object(g.createPopup)(n,j.gangMemberUpgradeBoxElements),j.gangMemberUpgradeBoxContent=document.getElementById(n+"-content"),j.gangMemberUpgradeBoxOpened=!0}},P.prototype.createGangMemberUpgradePanel=function(e,t){var n=Object(h.createElement)("div",{border:"1px solid white"}),i=Object(h.createElement)("h1",{innerText:this.name+" ("+this.task+")"});n.appendChild(i);var a=Object(h.createElement)("pre",{fontSize:"14px",display:"inline-block",width:"20%",innerText:"Hack: "+this.hack+" (x"+Object(y.formatNumber)(this.hack_mult*this.hack_asc_mult,2)+")\nStr: "+this.str+" (x"+Object(y.formatNumber)(this.str_mult*this.str_asc_mult,2)+")\nDef: "+this.def+" (x"+Object(y.formatNumber)(this.def_mult*this.def_asc_mult,2)+")\nDex: "+this.dex+" (x"+Object(y.formatNumber)(this.dex_mult*this.dex_asc_mult,2)+")\nAgi: "+this.agi+" (x"+Object(y.formatNumber)(this.agi_mult*this.agi_asc_mult,2)+")\nCha: "+this.cha+" (x"+Object(y.formatNumber)(this.cha_mult*this.cha_asc_mult,2)+")\n"});const r=[];function o(e){const t=B[e];null!=t?r.push(Object(h.createElement)("div",{class:"gang-owned-upgrade",innerText:e,tooltip:t.desc})):console.error(`Could not find GangMemberUpgrade object for name ${e}`)}for(const e of this.upgrades)o(e);for(const e of this.augmentations)o(e);var s=Object(h.createElement)("div",{class:"gang-owned-upgrades-div",innerText:"Purchased Upgrades:"});for(const e of r)s.appendChild(e);n.appendChild(a),n.appendChild(s),n.appendChild(Object(h.createElement)("br",{}));const l=[],u=[],p=[],m=[],d=[];for(let n in B)if(B.hasOwnProperty(n)){let i=B[n];if(t.money.lt(i.getCost(e)))continue;if(this.upgrades.includes(n)||this.augmentations.includes(n))continue;switch(i.type){case"w":l.push(i);break;case"a":u.push(i);break;case"v":p.push(i);break;case"r":m.push(i);break;case"g":d.push(i);break;default:console.error(`ERROR: Invalid Gang Member Upgrade Type: ${i.type}`)}}const g=Object(h.createElement)("div",{width:"20%",display:"inline-block"}),_=Object(h.createElement)("div",{width:"20%",display:"inline-block"}),f=Object(h.createElement)("div",{width:"20%",display:"inline-block"}),b=Object(h.createElement)("div",{width:"20%",display:"inline-block"}),E=Object(h.createElement)("div",{width:"20%",display:"inline-block"});g.appendChild(Object(h.createElement)("h2",{innerText:"Weapons"})),_.appendChild(Object(h.createElement)("h2",{innerText:"Armor"})),f.appendChild(Object(h.createElement)("h2",{innerText:"Vehicles"})),b.appendChild(Object(h.createElement)("h2",{innerText:"Rootkits"})),E.appendChild(Object(h.createElement)("h2",{innerText:"Augmentations"}));const v=[l,u,p,m,d],k=[g,_,f,b,E];for(let n=0;n(a.buyUpgrade(n,t,e),!1)};r>=3?s.tooltipleft=n.desc:s.tooltip=n.desc,i.appendChild(Object(h.createElement)("a",s))}(i[r],a,this,n,e)}}return n.appendChild(g),n.appendChild(_),n.appendChild(f),n.appendChild(b),n.appendChild(E),n};const j={gangContentCreated:!1,gangContainer:null,managementButton:null,territoryButton:null,gangManagementSubpage:null,gangTerritorySubpage:null,gangDesc:null,gangInfo:null,gangRecruitMemberButton:null,gangRecruitRequirementText:null,gangExpandAllButton:null,gangCollapseAllButton:null,gangMemberFilter:null,gangManageEquipmentButton:null,gangMemberList:null,gangMemberPanels:{},gangMemberUpgradeBoxOpened:!1,gangMemberUpgradeBox:null,gangMemberUpgradeBoxContent:null,gangMemberUpgradeBoxFilter:null,gangMemberUpgradeBoxDiscount:null,gangMemberUpgradeBoxElements:null,gangTerritoryDescText:null,gangTerritoryWarfareCheckbox:null,gangTerritoryWarfareCheckboxLabel:null,gangTerritoryWarfareClashChance:null,gangTerritoryDeathNotifyCheckbox:null,gangTerritoryDeathNotifyCheckboxLabel:null,gangTerritoryInfoText:null};M.prototype.displayGangContent=function(e){if(!j.gangContentCreated||null==j.gangContainer){j.gangContentCreated=!0,j.gangContainer=Object(h.createElement)("div",{id:"gang-container",class:"generic-menupage-container"});var t=this.facName;this.members,this.wanted,this.respect;j.gangContainer.appendChild(Object(h.createElement)("a",{class:"a-link-button",display:"inline-block",innerText:"Back",clickListener:()=>(r.Engine.loadFactionContent(),Object(l.a)(t),!1)})),j.managementButton=Object(h.createElement)("a",{id:"gang-management-subpage-button",class:"a-link-button-inactive",display:"inline-block",innerHTML:"Gang Management (Alt+1)",clickListener:()=>(j.gangManagementSubpage.style.display="block",j.gangTerritorySubpage.style.display="none",j.managementButton.classList.toggle("a-link-button-inactive"),j.managementButton.classList.toggle("a-link-button"),j.territoryButton.classList.toggle("a-link-button-inactive"),j.territoryButton.classList.toggle("a-link-button"),this.updateGangContent(),!1)}),j.territoryButton=Object(h.createElement)("a",{id:"gang-territory-subpage-button",class:"a-link-button",display:"inline-block",innerHTML:"Gang Territory (Alt+2)",clickListener:()=>(j.gangManagementSubpage.style.display="none",j.gangTerritorySubpage.style.display="block",j.managementButton.classList.toggle("a-link-button-inactive"),j.managementButton.classList.toggle("a-link-button"),j.territoryButton.classList.toggle("a-link-button-inactive"),j.territoryButton.classList.toggle("a-link-button"),this.updateGangContent(),!1)}),j.gangContainer.appendChild(j.managementButton),j.gangContainer.appendChild(j.territoryButton),j.gangManagementSubpage=Object(h.createElement)("div",{display:"block",id:"gang-management-subpage"});var n="";n=this.isHackingGang?"Ethical Hacking":"Vigilante Justice",j.gangDesc=Object(h.createElement)("p",{width:"70%",innerHTML:"This page is used to manage your gang members and get an overview of your gang's stats.

If a gang member is not earning much money or respect, the task that you have assigned to that member might be too difficult. Consider training that member's stats or choosing an easier task. The tasks closer to the top of the dropdown list are generally easier. Alternatively, the gang member's low production might be due to the fact that your wanted level is too high. Consider assigning a few members to the '"+n+"' task to lower your wanted level.

Installing Augmentations does NOT reset your progress with your Gang. Furthermore, after installing Augmentations, you will automatically be a member of whatever Faction you created your gang with.

You can also manage your gang programmatically through Netscript using the Gang API"}),j.gangManagementSubpage.appendChild(j.gangDesc),j.gangInfo=Object(h.createElement)("p",{id:"gang-info",width:"70%"}),j.gangManagementSubpage.appendChild(j.gangInfo),j.gangRecruitMemberButton=Object(h.createElement)("a",{id:"gang-management-recruit-member-btn",class:"a-link-button-inactive",innerHTML:"Recruit Gang Member",display:"inline-block",margin:"10px",clickListener:()=>{const e="recruit-gang-member-popup";let t;const n=Object(h.createElement)("p",{innerText:"Please enter a name for your new Gang member:"}),i=Object(h.createElement)("br"),a=Object(h.createElement)("input",{onkeyup:e=>{e.keyCode===m.KEY.ENTER&&t.click()},placeholder:"Name must be unique",type:"text"});t=Object(h.createElement)("a",{class:"std-button",clickListener:()=>{let t=a.value;return""===t?(Object(u.dialogBoxCreate)("You must enter a name for your Gang member!"),!1):this.canRecruitMember()?this.recruitMember(t)?(Object(k.removeElementById)(e),!1):(Object(u.dialogBoxCreate)("You already have a gang member with this name!"),!1):(Object(u.dialogBoxCreate)("You cannot recruit another Gang member!"),!1)},innerText:"Recruit Gang Member"});const r=Object(h.createElement)("a",{class:"std-button",clickListener:()=>(Object(k.removeElementById)(e),!1),innerText:"Cancel"});Object(g.createPopup)(e,[n,i,a,t,r])}}),j.gangManagementSubpage.appendChild(j.gangRecruitMemberButton),j.gangRecruitRequirementText=Object(h.createElement)("p",{color:"red",id:"gang-recruit-requirement-text",margin:"10px"}),j.gangManagementSubpage.appendChild(j.gangRecruitRequirementText),j.gangManagementSubpage.appendChild(Object(h.createElement)("br",{})),j.gangExpandAllButton=Object(h.createElement)("a",{class:"a-link-button",display:"inline-block",innerHTML:"Expand All",clickListener:()=>{for(var e=j.gangManagementSubpage.getElementsByClassName("accordion-header"),t=0;t{for(var e=j.gangManagementSubpage.getElementsByClassName("accordion-header"),t=0;t{this.displayGangMemberList()}}),j.gangManageEquipmentButton=Object(h.createElement)("a",{class:"a-link-button",display:"inline-block",innerHTML:"Manage Equipment",clickListener:()=>{this.createGangMemberUpgradeBox(e)}}),j.gangManagementSubpage.appendChild(j.gangExpandAllButton),j.gangManagementSubpage.appendChild(j.gangCollapseAllButton),j.gangManagementSubpage.appendChild(j.gangMemberFilter),j.gangManagementSubpage.appendChild(j.gangManageEquipmentButton),j.gangMemberList=Object(h.createElement)("ul",{id:"gang-member-list"}),this.displayGangMemberList(),j.gangManagementSubpage.appendChild(j.gangMemberList),j.gangTerritorySubpage=Object(h.createElement)("div",{id:"gang-territory-subpage",display:"none"}),j.gangTerritoryDescText=Object(h.createElement)("p",{width:"70%",innerHTML:"This page shows how much territory your Gang controls. This statistic is listed as a percentage, which represents how much of the total territory you control.

Every ~20 seconds, your gang has a chance to 'clash' with other gangs. Your chance to win a clash depends on your gang's power, which is listed in the display below. Your gang's power slowly accumulates over time. The accumulation rate is determined by the stats of all Gang members you have assigned to the 'Territory Warfare' task. Gang members that are not assigned to this task do not contribute to your gang's power. Your gang also loses a small amount of power whenever you lose a clash

NOTE: Gang members assigned to 'Territory Warfare' can be killed during clashes. This can happen regardless of whether you win or lose the clash. A gang member being killed results in both respect and power loss for your gang.

The amount of territory you have affects all aspects of your Gang members' production, including money, respect, and wanted level. It is very beneficial to have high territory control.

"}),j.gangTerritorySubpage.appendChild(j.gangTerritoryDescText),j.gangTerritoryWarfareCheckbox=Object(h.createElement)("input",{display:"inline-block",id:"gang-management-territory-warfare-checkbox",changeListener:()=>{this.territoryWarfareEngaged=j.gangTerritoryWarfareCheckbox.checked},margin:"2px",type:"checkbox"}),j.gangTerritoryWarfareCheckbox.checked=this.territoryWarfareEngaged,j.gangTerritoryWarfareCheckboxLabel=Object(h.createElement)("label",{color:"white",display:"inline-block",for:"gang-management-territory-warfare-checkbox",innerText:"Engage in Territory Warfare",tooltip:"Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance to gradually decrease until it reaches 0%"}),j.gangTerritorySubpage.appendChild(j.gangTerritoryWarfareCheckbox),j.gangTerritorySubpage.appendChild(j.gangTerritoryWarfareCheckboxLabel),j.gangTerritorySubpage.appendChild(Object(h.createElement)("br")),j.gangTerritoryWarfareClashChance=Object(h.createElement)("p",{display:"inline-block"}),j.gangTerritorySubpage.appendChild(j.gangTerritoryWarfareClashChance),j.gangTerritorySubpage.appendChild(Object(h.createElement)("div",{class:"help-tip",display:"inline-block",innerText:"?",clickListener:()=>{Object(u.dialogBoxCreate)("This percentage represents the chance you have of 'clashing' with with another gang. If you do not wish to gain/lose territory, then keep this percentage at 0% by not engaging in territory warfare.")}})),j.gangTerritoryDeathNotifyCheckbox=Object(h.createElement)("input",{display:"inline-block",id:"gang-management-notify-member-death-checkbox",changeListener:()=>{this.notifyMemberDeath=j.gangTerritoryDeathNotifyCheckbox.checked},margin:"2px",type:"checkbox"}),j.gangTerritoryDeathNotifyCheckbox.checked=this.notifyMemberDeath,j.gangTerritoryDeathNotifyCheckboxLabel=Object(h.createElement)("label",{color:"white",display:"inline-block",for:"gang-management-notify-member-death-checkbox",innerText:"Notify about Gang Member Deaths",tooltip:"If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members dies in a territory clash."}),j.gangTerritorySubpage.appendChild(Object(h.createElement)("br")),j.gangTerritorySubpage.appendChild(j.gangTerritoryDeathNotifyCheckbox),j.gangTerritorySubpage.appendChild(j.gangTerritoryDeathNotifyCheckboxLabel),j.gangTerritorySubpage.appendChild(Object(h.createElement)("br"));var i=Object(h.createElement)("fieldset",{display:"block",margin:"6px",width:"50%"});j.gangTerritoryInfoText=Object(h.createElement)("p"),i.appendChild(j.gangTerritoryInfoText),j.gangTerritorySubpage.appendChild(i),j.gangContainer.appendChild(j.gangTerritorySubpage),j.gangContainer.appendChild(j.gangManagementSubpage),document.getElementById("entire-game-container").appendChild(j.gangContainer)}j.gangContainer.style.display="block",this.updateGangContent()},M.prototype.displayGangMemberList=function(){Object(E.removeChildrenFromElement)(j.gangMemberList),j.gangMemberPanels={};const e=this.members,t=j.gangMemberFilter.value.toString();for(var n=0;n-1||e[n].task.indexOf(t)>-1)&&this.createGangMemberDisplayElement(e[n])},M.prototype.updateGangContent=function(){if(j.gangContentCreated)if(j.gangMemberUpgradeBoxOpened&&(j.gangMemberUpgradeBoxDiscount.childNodes[0].nodeValue="Discount: -"+c.numeralWrapper.format(1-1/this.getDiscount(),"0.00%")),"block"===j.gangTerritorySubpage.style.display){j.gangTerritoryWarfareClashChance.innerText=`Territory Clash Chance: ${c.numeralWrapper.format(this.territoryClashChance,"0.000%")}`,j.gangTerritoryWarfareCheckbox.checked=this.territoryWarfareEngaged,j.gangTerritoryInfoText.innerHTML="";const e=O[this.facName].power;for(const t in O)if(O.hasOwnProperty(t)){const n=O[t];let i,a=100*n.territory;if(i=a<=0?Object(y.formatNumber)(0,2):a>=100?Object(y.formatNumber)(100,2):Object(y.formatNumber)(a,2),t===this.facName){let e=`${t}
Power: ${Object(y.formatNumber)(n.power,6)}
`;e+=`Territory: ${i}%

`,j.gangTerritoryInfoText.innerHTML+=e}else{const a=e/(n.power+e);let r=`${t}
Power: ${Object(y.formatNumber)(n.power,6)}
`;r+=`Territory: ${i}%
`,r+=`Chance to win clash with this gang: ${c.numeralWrapper.format(a,"0.000%")}

`,j.gangTerritoryInfoText.innerHTML+=r}}}else{if(j.gangInfo instanceof Element){var e,t=s.Factions[this.facName];e=t instanceof o.Faction?t.playerReputation:"ERROR",Object(E.removeChildrenFromElement)(j.gangInfo),j.gangInfo.appendChild(Object(h.createElement)("p",{display:"inline-block",innerText:"Respect: "+Object(y.formatNumber)(this.respect,6)+" ("+Object(y.formatNumber)(5*this.respectGainRate,6)+" / sec)",tooltip:"Represents the amount of respect your gang has from other gangs and criminal organizations. Your respect affects the amount of money your gang members will earn, and also determines how much reputation you are earning with your gang's corresponding Faction."})),j.gangInfo.appendChild(Object(h.createElement)("br")),j.gangInfo.appendChild(Object(h.createElement)("p",{display:"inline-block",innerText:"Wanted Level: "+Object(y.formatNumber)(this.wanted,6)+" ("+Object(y.formatNumber)(5*this.wantedGainRate,6)+" / sec)",tooltip:"Represents how much the gang is wanted by law enforcement. The higher your gang's wanted level, the harder it will be for your gang members to make money and earn respect. Note that the minimum wanted level is 1."})),j.gangInfo.appendChild(Object(h.createElement)("br"));var n=this.getWantedPenalty();n=100*(1-n),j.gangInfo.appendChild(Object(h.createElement)("p",{display:"inline-block",innerText:`Wanted Level Penalty: -${Object(y.formatNumber)(n,2)}%`,tooltip:"Penalty for respect and money gain rates due to Wanted Level"})),j.gangInfo.appendChild(Object(h.createElement)("br")),j.gangInfo.appendChild(Object(h.createElement)("p",{display:"inline-block",innerText:`Money gain rate: ${c.numeralWrapper.format(5*this.moneyGainRate,"$0.000a")} / sec`})),j.gangInfo.appendChild(Object(h.createElement)("br"));var i=100*O[this.facName].territory;let a;a=i<=0?Object(y.formatNumber)(0,2):i>=100?Object(y.formatNumber)(100,2):Object(y.formatNumber)(i,2),j.gangInfo.appendChild(Object(h.createElement)("p",{display:"inline-block",innerText:`Territory: ${Object(y.formatNumber)(a,3)}%`,tooltip:"The percentage of total territory your Gang controls"})),j.gangInfo.appendChild(Object(h.createElement)("br")),j.gangInfo.appendChild(Object(h.createElement)("p",{display:"inline-block",innerText:"Faction reputation: "+Object(y.formatNumber)(e,3)})),j.gangInfo.appendChild(Object(h.createElement)("br"));const l=1e3/r.Engine._idleSpeed;j.gangInfo.appendChild(Object(h.createElement)("p",{innerText:`Bonus time(s): ${this.storedCycles/l}`,display:"inline-block",tooltip:"You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by the browser). Bonus time makes the Gang mechanic progress faster, up to 5x the normal speed"})),j.gangInfo.appendChild(Object(h.createElement)("br"))}else console.error("gang-info DOM element DNE");const a=this.members.length,l=this.getRespectNeededToRecruitMember(),u=j.gangRecruitMemberButton;a>=30?(u.className="a-link-button-inactive",j.gangRecruitRequirementText.style.display="inline-block",j.gangRecruitRequirementText.innerHTML="You have reached the maximum amount of gang members"):this.canRecruitMember()?(u.className="a-link-button",j.gangRecruitRequirementText.style.display="none"):(u.className="a-link-button-inactive",j.gangRecruitRequirementText.style.display="inline-block",j.gangRecruitRequirementText.innerHTML=`${Object(y.formatNumber)(l,2)} respect needed to recruit next member`);for(let e=0;e")});j.gangMemberPanels[t].statsDiv=r;const o=Object(h.createElement)("pre",{display:"inline",id:t+"gang-member-stats-text"}),s=Object(h.createElement)("br"),l=Object(h.createElement)("button",{class:"accordion-button",innerText:"Ascend",clickListener:()=>{const t=`gang-management-ascend-member ${e.name}`,n=e.getAscensionResults(),i=Object(h.createElement)("pre",{innerText:["Are you sure you want to ascend this member? (S)he will lose all of","his non-Augmentation upgrades and his/her stats will reset back to 1.","",`Furthermore, your gang will lose ${c.numeralWrapper.format(e.earnedRespect,"0.000000")} respect`,"","In return, (s)he will gain the following permanent boost to stat multipliers:\n",`Hacking: +${c.numeralWrapper.format(n.hack,"0.00%")}`,`Strength: +${c.numeralWrapper.format(n.str,"0.00%")}`,`Defense: +${c.numeralWrapper.format(n.def,"0.00%")}`,`Dexterity: +${c.numeralWrapper.format(n.dex,"0.00%")}`,`Agility: +${c.numeralWrapper.format(n.agi,"0.00%")}`,`Charisma: +${c.numeralWrapper.format(n.cha,"0.00%")}`].join("\n")}),a=Object(h.createElement)("button",{class:"std-button",clickListener:()=>(this.ascendMember(e),this.updateGangMemberDisplayElement(e),Object(k.removeElementById)(t),!1),innerText:"Ascend"}),r=Object(h.createElement)("button",{class:"std-button",clickListener:()=>(Object(k.removeElementById)(t),!1),innerText:"Cancel"});Object(g.createPopup)(t,[i,a,r])}}),p=Object(h.createElement)("div",{class:"help-tip",clickListener:()=>{Object(u.dialogBoxCreate)(["Ascending a Gang Member resets the member's progress and stats in exchange","for a permanent boost to their stat multipliers.","

The additional stat multiplier that the Gang Member gains upon ascension","is based on the amount of multipliers the member has from non-Augmentation Equipment.","

Upon ascension, the member will lose all of its non-Augmentation Equipment and your","gang will lose respect equal to the total respect earned by the member."].join(" "))},innerText:"?",marginTop:"5px"});r.appendChild(o),r.appendChild(s),r.appendChild(l),r.appendChild(p);const m=Object(h.createElement)("div",{class:"gang-member-info-div",id:t+"gang-member-task"}),_=Object(h.createElement)("select",{id:t+"gang-member-task-selector"});let y=this.getAllTaskNames();y.unshift("---");for(var f=0;f{var t=_.options[_.selectedIndex].text;e.assignToTask(t),this.setGangMemberTaskDescription(e,t),this.updateGangContent()}),w.hasOwnProperty(e.task)){var E=e.task,v=0;for(let e=0;e"))}var a=document.getElementById(t+"gang-member-gain-info");a&&(a.innerHTML=[`Money: $ ${Object(y.formatNumber)(5*e.calculateMoneyGain(this),2)} / sec`,`Respect: ${Object(y.formatNumber)(5*e.calculateRespectGain(this),6)} / sec`,`Wanted Level: ${Object(y.formatNumber)(5*e.calculateWantedLevelGain(this),6)} / sec`,`Total Respect Earned: ${Object(y.formatNumber)(e.earnedRespect,6)}`].join("
"));const r=document.getElementById(t+"gang-member-task-selector");if(r){let t=this.getAllTaskNames();if(t.unshift("---"),w.hasOwnProperty(e.task)){const n=e.task;let i=0;for(let e=0;e["+(_Fconf__WEBPACK_IMPORTED_MODULE_7__.a.ENABLE_TIMESTAMPS?Object(_utils_helpers_getTimestamp__WEBPACK_IMPORTED_MODULE_29__.getTimestamp)()+" ":"")+_Player__WEBPACK_IMPORTED_MODULE_14__.a.getCurrentServer().hostname+" ~]> "+n),n.length>0&&(Terminal.resetTerminalInput(),Terminal.executeCommands(n))}if(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.C&&e.ctrlKey&&(_engine__WEBPACK_IMPORTED_MODULE_6__.Engine._actionInProgress?(Object(_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_32__.post)("Cancelling..."),_engine__WEBPACK_IMPORTED_MODULE_6__.Engine._actionInProgress=!1,Terminal.finishAction(!0)):_Fconf__WEBPACK_IMPORTED_MODULE_7__.a.ENABLE_BASH_HOTKEYS&&Terminal.resetTerminalInput()),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.L&&e.ctrlKey&&(e.preventDefault(),Terminal.executeCommand("clear")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.UPARROW||_Fconf__WEBPACK_IMPORTED_MODULE_7__.a.ENABLE_BASH_HOTKEYS&&e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.P&&e.ctrlKey){if(_Fconf__WEBPACK_IMPORTED_MODULE_7__.a.ENABLE_BASH_HOTKEYS&&e.preventDefault(),null==t)return;var i=Terminal.commandHistoryIndex;if(0==(r=Terminal.commandHistory.length))return;(i<0||i>r)&&(Terminal.commandHistoryIndex=r),0!=i&&--Terminal.commandHistoryIndex;var a=Terminal.commandHistory[Terminal.commandHistoryIndex];t.value=a,Object(_utils_SetTimeoutRef__WEBPACK_IMPORTED_MODULE_21__.setTimeoutRef)(function(){t.selectionStart=t.selectionEnd=1e4},0)}if(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.DOWNARROW||_Fconf__WEBPACK_IMPORTED_MODULE_7__.a.ENABLE_BASH_HOTKEYS&&e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.M&&e.ctrlKey){if(_Fconf__WEBPACK_IMPORTED_MODULE_7__.a.ENABLE_BASH_HOTKEYS&&e.preventDefault(),null==t)return;var r;i=Terminal.commandHistoryIndex;if(0==(r=Terminal.commandHistory.length))return;if((i<0||i>r)&&(Terminal.commandHistoryIndex=r),i==r||i==r-1)Terminal.commandHistoryIndex=r,t.value="";else{++Terminal.commandHistoryIndex;a=Terminal.commandHistory[Terminal.commandHistoryIndex];t.value=a}}if(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.TAB){if(e.preventDefault(),null==t)return;var o=t.value;if(""==o)return;const i=o.lastIndexOf(";");-1!==i&&(o=o.slice(i+1));var s=(o=(o=o.trim()).replace(/\s\s+/g," ")).split(" "),l=s.length-2;l<-1&&(l=0);var c=determineAllPossibilitiesForTabCompletion(o,l);if(0==c.length)return;var u="";n="";if(0==s.length)return;1==s.length?n=s[0]:2==s.length?(n=s[0],u=s[1]):3==s.length?(n=s[0]+" "+s[1],u=s[2]):(u=s.pop(),n=s.join(" ")),tabCompletion(n,u,c),t.focus()}_Fconf__WEBPACK_IMPORTED_MODULE_7__.a.ENABLE_BASH_HOTKEYS&&(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.A&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("home")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.E&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("end")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.B&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("prevchar")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.B&&e.altKey&&(e.preventDefault(),Terminal.moveTextCursor("prevword")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.F&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("nextchar")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.F&&e.altKey&&(e.preventDefault(),Terminal.moveTextCursor("nextword")),e.keyCode!==_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.H&&e.keyCode!==_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_25__.KEY.D||!e.ctrlKey||(Terminal.modifyInput("backspace"),e.preventDefault()))}});let terminalCtrlPressed=!1,shiftKeyPressed=!1;function tabCompletion(e,t,n,i=0){if(n.constructor!==Array)return;if(!Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_22__.containsAllStrings)(n))return;if(""==t)for(var a=n.length-1;a>=0;--a)n[a].toLowerCase().startsWith(e.toLowerCase())||n.splice(a,1);else for(a=n.length-1;a>=0;--a)n[a].toLowerCase().startsWith(t.toLowerCase())||n.splice(a,1);const r=document.getElementById("terminal-input-text-box");if(null==r)return void console.warn("Couldn't find terminal input DOM element (id=terminal-input-text-box) when trying to autocomplete");const o=r.value.lastIndexOf(";");var s="";if(0!=n.length)if(1==n.length)s=""==t?n[0]+" ":e+" "+n[0],r.value=-1===o?s:r.value.slice(0,o+1)+" "+s,r.focus();else{var l=Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_22__.longestCommonStart)(n),c="";for(a=0;a "+e),Object(_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_32__.post)(c)):(r.value=-1===o?l:r.value.slice(0,o+1)+" "+l,r.focus()):l==t?(Object(_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_32__.post)("> "+e+" "+t),Object(_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_32__.post)(c)):(r.value=-1==o?e+" "+l:r.value.slice(0,o+1)+" "+e+" "+l,r.focus())}}function determineAllPossibilitiesForTabCompletion(e,t=0){var n=[];n=n.concat(Object.keys(_Alias__WEBPACK_IMPORTED_MODULE_0__.b));var i=_Player__WEBPACK_IMPORTED_MODULE_14__.a.getCurrentServer();if((e=e.toLowerCase()).startsWith("./")&&-1==t){for(var a=0;a["+_Player__WEBPACK_IMPORTED_MODULE_14__.a.getCurrentServer().hostname+' ~]$
",v.noCloneChecked=!!ye.cloneNode(!0).lastChild.defaultValue;var Ce=s.documentElement,ke=/^key/,Ae=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function _e(){return!0}function Se(){return!1}function Fe(){try{return s.activeElement}catch(e){}}function De(e,t,n,r,i,o){var s,a;if("object"==typeof t){for(a in"string"!=typeof n&&(r=r||n,n=void 0),t)De(e,a,n,r,t[a],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(s=i,(i=function(e){return k().off(e),s.apply(this,arguments)}).guid=s.guid||(s.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}k.event={global:{},add:function(e,t,n,r,i){var o,s,a,l,u,c,h,d,f,p,g,m=J.get(e);if(m)for(n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(Ce,i),n.guid||(n.guid=k.guid++),(l=m.events)||(l=m.events={}),(s=m.handle)||(s=m.handle=function(t){return void 0!==k&&k.event.triggered!==t.type?k.event.dispatch.apply(e,arguments):void 0}),u=(t=(t||"").match(I)||[""]).length;u--;)f=g=(a=Ee.exec(t[u])||[])[1],p=(a[2]||"").split(".").sort(),f&&(h=k.event.special[f]||{},f=(i?h.delegateType:h.bindType)||f,h=k.event.special[f]||{},c=k.extend({type:f,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:p.join(".")},o),(d=l[f])||((d=l[f]=[]).delegateCount=0,h.setup&&!1!==h.setup.call(e,r,p,s)||e.addEventListener&&e.addEventListener(f,s)),h.add&&(h.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),k.event.global[f]=!0)},remove:function(e,t,n,r,i){var o,s,a,l,u,c,h,d,f,p,g,m=J.hasData(e)&&J.get(e);if(m&&(l=m.events)){for(u=(t=(t||"").match(I)||[""]).length;u--;)if(f=g=(a=Ee.exec(t[u])||[])[1],p=(a[2]||"").split(".").sort(),f){for(h=k.event.special[f]||{},d=l[f=(r?h.delegateType:h.bindType)||f]||[],a=a[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=d.length;o--;)c=d[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,h.remove&&h.remove.call(e,c));s&&!d.length&&(h.teardown&&!1!==h.teardown.call(e,p,m.handle)||k.removeEvent(e,f,m.handle),delete l[f])}else for(f in l)k.event.remove(e,f+t[u],n,r,!0);k.isEmptyObject(l)&&J.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,s,a=k.event.fix(e),l=new Array(arguments.length),u=(J.get(this,"events")||{})[a.type]||[],c=k.event.special[a.type]||{};for(l[0]=a,t=1;t=1))for(;u!==this;u=u.parentNode||this)if(1===u.nodeType&&("click"!==e.type||!0!==u.disabled)){for(o=[],s={},n=0;n-1:k.find(i,this,null,[u]).length),s[i]&&o.push(r);o.length&&a.push({elem:u,handlers:o})}return u=this,l\x20\t\r\n\f]*)[^>]*)\/>/gi,Be=/\s*$/g;function Oe(e,t){return T(e,"table")&&T(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function je(e,t){var n,r,i,o,s,a,l,u;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),s=J.set(t,o),u=o.events))for(i in delete s.handle,s.events={},u)for(n=0,r=u[i].length;n1&&"string"==typeof p&&!v.checkClone&&Le.test(p))return e.each(function(i){var o=e.eq(i);g&&(t[0]=p.call(this,i,o.html())),Ie(o,t,n,r)});if(d&&(o=(i=xe(t,e[0].ownerDocument,!1,e,r)).firstChild,1===i.childNodes.length&&(i=o),o||r)){for(a=(s=k.map(me(i,"script"),Pe)).length;h")},clone:function(e,t,n){var r,i,o,s,a,l,u,c=e.cloneNode(!0),h=k.contains(e.ownerDocument,e);if(!(v.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(s=me(c),r=0,i=(o=me(e)).length;r0&&ve(s,!h&&me(e,"script")),c},cleanData:function(e){for(var t,n,r,i=k.event.special,o=0;void 0!==(n=e[o]);o++)if(X(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?k.event.remove(n,r):k.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),k.fn.extend({detach:function(e){return Ne(this,e,!0)},remove:function(e){return Ne(this,e)},text:function(e){return V(this,function(e){return void 0===e?k.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Ie(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Oe(this,e).appendChild(e)})},prepend:function(){return Ie(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Oe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Ie(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Ie(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(k.cleanData(me(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return k.clone(this,e,t)})},html:function(e){return V(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Be.test(e)&&!ge[(fe.exec(e)||["",""])[1].toLowerCase()]){e=k.htmlPrefilter(e);try{for(;n=0&&(l+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-l-a-.5))),l}function et(e,t,n){var r=We(e),i=ze(e,t,r),o="border-box"===k.css(e,"boxSizing",!1,r),s=o;if($e.test(i)){if(!n)return i;i="auto"}return s=s&&(v.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===k.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],s=!0),(i=parseFloat(i)||0)+Qe(e,t,n||(o?"border":"content"),s,r,i)+"px"}function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}k.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=ze(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=Y(t),l=Ge.test(t),u=e.style;if(l||(t=Ze(a)),s=k.cssHooks[t]||k.cssHooks[a],void 0===n)return s&&"get"in s&&void 0!==(i=s.get(e,!1,r))?i:u[t];"string"===(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=le(e,t,i),o="number"),null!=n&&n==n&&("number"===o&&(n+=i&&i[3]||(k.cssNumber[a]?"":"px")),v.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&void 0===(n=s.set(e,n,r))||(l?u.setProperty(t,n):u[t]=n))}},css:function(e,t,n,r){var i,o,s,a=Y(t);return Ge.test(t)||(t=Ze(a)),(s=k.cssHooks[t]||k.cssHooks[a])&&"get"in s&&(i=s.get(e,!0,n)),void 0===i&&(i=ze(e,t,r)),"normal"===i&&t in qe&&(i=qe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),k.each(["height","width"],function(e,t){k.cssHooks[t]={get:function(e,n,r){if(n)return!Ve.test(k.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):ae(e,Ke,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=We(e),s="border-box"===k.css(e,"boxSizing",!1,o),a=r&&Qe(e,t,r,s,o);return s&&v.scrollboxSize()===o.position&&(a-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Qe(e,t,"border",!1,o)-.5)),a&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=k.css(e,t)),Je(0,n,a)}}}),k.cssHooks.marginLeft=Ue(v.reliableMarginLeft,function(e,t){if(t)return(parseFloat(ze(e,"marginLeft"))||e.getBoundingClientRect().left-ae(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),k.each({margin:"",padding:"",border:"Width"},function(e,t){k.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(k.cssHooks[e+t].set=Je)}),k.fn.extend({css:function(e,t){return V(this,function(e,t,n){var r,i,o={},s=0;if(Array.isArray(t)){for(r=We(e),i=t.length;s1)}}),k.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||k.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(k.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=k.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=k.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){k.fx.step[e.prop]?k.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[k.cssProps[e.prop]]&&!k.cssHooks[e.prop]?e.elem[e.prop]=e.now:k.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},k.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},k.fx=tt.prototype.init,k.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function st(){rt&&(!1===s.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(st):n.setTimeout(st,k.fx.interval),k.fx.tick())}function at(){return n.setTimeout(function(){nt=void 0}),nt=Date.now()}function lt(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function ut(e,t,n){for(var r,i=(ct.tweeners[t]||[]).concat(ct.tweeners["*"]),o=0,s=i.length;o1)},removeAttr:function(e){return this.each(function(){k.removeAttr(this,e)})}}),k.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?k.prop(e,t,n):(1===o&&k.isXMLDoc(e)||(i=k.attrHooks[t.toLowerCase()]||(k.expr.match.bool.test(t)?ht:void 0)),void 0!==n?null===n?void k.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=k.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!v.radioValue&&"radio"===t&&T(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(I);if(i&&1===e.nodeType)for(;n=i[r++];)e.removeAttribute(n)}}),ht={set:function(e,t,n){return!1===t?k.removeAttr(e,n):e.setAttribute(n,n),n}},k.each(k.expr.match.bool.source.match(/\w+/g),function(e,t){var n=dt[t]||k.find.attr;dt[t]=function(e,t,r){var i,o,s=t.toLowerCase();return r||(o=dt[s],dt[s]=i,i=null!=n(e,t,r)?s:null,dt[s]=o),i}});var ft=/^(?:input|select|textarea|button)$/i,pt=/^(?:a|area)$/i;function gt(e){return(e.match(I)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function vt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(I)||[]}k.fn.extend({prop:function(e,t){return V(this,k.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[k.propFix[e]||e]})}}),k.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&k.isXMLDoc(e)||(t=k.propFix[t]||t,i=k.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=k.find.attr(e,"tabindex");return t?parseInt(t,10):ft.test(e.nodeName)||pt.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),v.optSelected||(k.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),k.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){k.propFix[this.toLowerCase()]=this}),k.fn.extend({addClass:function(e){var t,n,r,i,o,s,a,l=0;if(y(e))return this.each(function(t){k(this).addClass(e.call(this,t,mt(this)))});if((t=vt(e)).length)for(;n=this[l++];)if(i=mt(n),r=1===n.nodeType&&" "+gt(i)+" "){for(s=0;o=t[s++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(a=gt(r))&&n.setAttribute("class",a)}return this},removeClass:function(e){var t,n,r,i,o,s,a,l=0;if(y(e))return this.each(function(t){k(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=vt(e)).length)for(;n=this[l++];)if(i=mt(n),r=1===n.nodeType&&" "+gt(i)+" "){for(s=0;o=t[s++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");i!==(a=gt(r))&&n.setAttribute("class",a)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):y(e)?this.each(function(n){k(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,s;if(r)for(i=0,o=k(this),s=vt(e);t=s[i++];)o.hasClass(t)?o.removeClass(t):o.addClass(t);else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+gt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var yt=/\r/g;k.fn.extend({val:function(e){var t,n,r,i=this[0];return arguments.length?(r=y(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,k(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=k.map(i,function(e){return null==e?"":e+""})),(t=k.valHooks[this.type]||k.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))})):i?(t=k.valHooks[i.type]||k.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(yt,""):null==n?"":n:void 0}}),k.extend({valHooks:{option:{get:function(e){var t=k.find.attr(e,"value");return null!=t?t:gt(k.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,s="select-one"===e.type,a=s?null:[],l=s?o+1:i.length;for(r=o<0?l:s?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),k.each(["radio","checkbox"],function(){k.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=k.inArray(k(e).val(),t)>-1}},v.checkOn||(k.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),v.focusin="onfocusin"in n;var bt=/^(?:focusinfocus|focusoutblur)$/,wt=function(e){e.stopPropagation()};k.extend(k.event,{trigger:function(e,t,r,i){var o,a,l,u,c,h,d,f,g=[r||s],m=p.call(e,"type")?e.type:e,v=p.call(e,"namespace")?e.namespace.split("."):[];if(a=f=l=r=r||s,3!==r.nodeType&&8!==r.nodeType&&!bt.test(m+k.event.triggered)&&(m.indexOf(".")>-1&&(m=(v=m.split(".")).shift(),v.sort()),c=m.indexOf(":")<0&&"on"+m,(e=e[k.expando]?e:new k.Event(m,"object"==typeof e&&e)).isTrigger=i?2:3,e.namespace=v.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+v.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=r),t=null==t?[e]:k.makeArray(t,[e]),d=k.event.special[m]||{},i||!d.trigger||!1!==d.trigger.apply(r,t))){if(!i&&!d.noBubble&&!b(r)){for(u=d.delegateType||m,bt.test(u+m)||(a=a.parentNode);a;a=a.parentNode)g.push(a),l=a;l===(r.ownerDocument||s)&&g.push(l.defaultView||l.parentWindow||n)}for(o=0;(a=g[o++])&&!e.isPropagationStopped();)f=a,e.type=o>1?u:d.bindType||m,(h=(J.get(a,"events")||{})[e.type]&&J.get(a,"handle"))&&h.apply(a,t),(h=c&&a[c])&&h.apply&&X(a)&&(e.result=h.apply(a,t),!1===e.result&&e.preventDefault());return e.type=m,i||e.isDefaultPrevented()||d._default&&!1!==d._default.apply(g.pop(),t)||!X(r)||c&&y(r[m])&&!b(r)&&((l=r[c])&&(r[c]=null),k.event.triggered=m,e.isPropagationStopped()&&f.addEventListener(m,wt),r[m](),e.isPropagationStopped()&&f.removeEventListener(m,wt),k.event.triggered=void 0,l&&(r[c]=l)),e.result}},simulate:function(e,t,n){var r=k.extend(new k.Event,n,{type:e,isSimulated:!0});k.event.trigger(r,null,t)}}),k.fn.extend({trigger:function(e,t){return this.each(function(){k.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return k.event.trigger(e,t,n,!0)}}),v.focusin||k.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){k.event.simulate(t,e.target,k.event.fix(e))};k.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var xt=n.location,Ct=Date.now(),kt=/\?/;k.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new n.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||k.error("Invalid XML: "+e),t};var At=/\[\]$/,Et=/\r?\n/g,_t=/^(?:submit|button|image|reset|file)$/i,St=/^(?:input|select|textarea|keygen)/i;function Ft(e,t,n,r){var i;if(Array.isArray(t))k.each(t,function(t,i){n||At.test(e)?r(e,i):Ft(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==C(t))r(e,t);else for(i in t)Ft(e+"["+i+"]",t[i],n,r)}k.param=function(e,t){var n,r=[],i=function(e,t){var n=y(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!k.isPlainObject(e))k.each(e,function(){i(this.name,this.value)});else for(n in e)Ft(n,e[n],t,i);return r.join("&")},k.fn.extend({serialize:function(){return k.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=k.prop(this,"elements");return e?k.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!k(this).is(":disabled")&&St.test(this.nodeName)&&!_t.test(e)&&(this.checked||!de.test(e))}).map(function(e,t){var n=k(this).val();return null==n?null:Array.isArray(n)?k.map(n,function(e){return{name:t.name,value:e.replace(Et,"\r\n")}}):{name:t.name,value:n.replace(Et,"\r\n")}}).get()}});var Dt=/%20/g,Tt=/#.*$/,Bt=/([?&])_=[^&]*/,Lt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Mt=/^(?:GET|HEAD)$/,Ot=/^\/\//,Pt={},Rt={},jt="*/".concat("*"),It=s.createElement("a");function Nt(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(I)||[];if(y(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function $t(e,t,n,r){var i={},o=e===Rt;function s(a){var l;return i[a]=!0,k.each(e[a]||[],function(e,a){var u=a(t,n,r);return"string"!=typeof u||o||i[u]?o?!(l=u):void 0:(t.dataTypes.unshift(u),s(u),!1)}),l}return s(t.dataTypes[0])||!i["*"]&&s("*")}function Wt(e,t){var n,r,i=k.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&k.extend(!0,e,r),e}It.href=xt.href,k.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:xt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(xt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":jt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":k.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Wt(Wt(e,k.ajaxSettings),t):Wt(k.ajaxSettings,e)},ajaxPrefilter:Nt(Pt),ajaxTransport:Nt(Rt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var r,i,o,a,l,u,c,h,d,f,p=k.ajaxSetup({},t),g=p.context||p,m=p.context&&(g.nodeType||g.jquery)?k(g):k.event,v=k.Deferred(),y=k.Callbacks("once memory"),b=p.statusCode||{},w={},x={},C="canceled",A={readyState:0,getResponseHeader:function(e){var t;if(c){if(!a)for(a={};t=Lt.exec(o);)a[t[1].toLowerCase()]=t[2];t=a[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?o:null},setRequestHeader:function(e,t){return null==c&&(e=x[e.toLowerCase()]=x[e.toLowerCase()]||e,w[e]=t),this},overrideMimeType:function(e){return null==c&&(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)A.always(e[A.status]);else for(t in e)b[t]=[b[t],e[t]];return this},abort:function(e){var t=e||C;return r&&r.abort(t),E(0,t),this}};if(v.promise(A),p.url=((e||p.url||xt.href)+"").replace(Ot,xt.protocol+"//"),p.type=t.method||t.type||p.method||p.type,p.dataTypes=(p.dataType||"*").toLowerCase().match(I)||[""],null==p.crossDomain){u=s.createElement("a");try{u.href=p.url,u.href=u.href,p.crossDomain=It.protocol+"//"+It.host!=u.protocol+"//"+u.host}catch(e){p.crossDomain=!0}}if(p.data&&p.processData&&"string"!=typeof p.data&&(p.data=k.param(p.data,p.traditional)),$t(Pt,p,t,A),c)return A;for(d in(h=k.event&&p.global)&&0==k.active++&&k.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Mt.test(p.type),i=p.url.replace(Tt,""),p.hasContent?p.data&&p.processData&&0===(p.contentType||"").indexOf("application/x-www-form-urlencoded")&&(p.data=p.data.replace(Dt,"+")):(f=p.url.slice(i.length),p.data&&(p.processData||"string"==typeof p.data)&&(i+=(kt.test(i)?"&":"?")+p.data,delete p.data),!1===p.cache&&(i=i.replace(Bt,"$1"),f=(kt.test(i)?"&":"?")+"_="+Ct+++f),p.url=i+f),p.ifModified&&(k.lastModified[i]&&A.setRequestHeader("If-Modified-Since",k.lastModified[i]),k.etag[i]&&A.setRequestHeader("If-None-Match",k.etag[i])),(p.data&&p.hasContent&&!1!==p.contentType||t.contentType)&&A.setRequestHeader("Content-Type",p.contentType),A.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+jt+"; q=0.01":""):p.accepts["*"]),p.headers)A.setRequestHeader(d,p.headers[d]);if(p.beforeSend&&(!1===p.beforeSend.call(g,A,p)||c))return A.abort();if(C="abort",y.add(p.complete),A.done(p.success),A.fail(p.error),r=$t(Rt,p,t,A)){if(A.readyState=1,h&&m.trigger("ajaxSend",[A,p]),c)return A;p.async&&p.timeout>0&&(l=n.setTimeout(function(){A.abort("timeout")},p.timeout));try{c=!1,r.send(w,E)}catch(e){if(c)throw e;E(-1,e)}}else E(-1,"No Transport");function E(e,t,s,a){var u,d,f,w,x,C=t;c||(c=!0,l&&n.clearTimeout(l),r=void 0,o=a||"",A.readyState=e>0?4:0,u=e>=200&&e<300||304===e,s&&(w=function(e,t,n){for(var r,i,o,s,a=e.contents,l=e.dataTypes;"*"===l[0];)l.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in a)if(a[i]&&a[i].test(r)){l.unshift(i);break}if(l[0]in n)o=l[0];else{for(i in n){if(!l[0]||e.converters[i+" "+l[0]]){o=i;break}s||(s=i)}o=o||s}if(o)return o!==l[0]&&l.unshift(o),n[o]}(p,A,s)),w=function(e,t,n,r){var i,o,s,a,l,u={},c=e.dataTypes.slice();if(c[1])for(s in e.converters)u[s.toLowerCase()]=e.converters[s];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(!(s=u[l+" "+o]||u["* "+o]))for(i in u)if((a=i.split(" "))[1]===o&&(s=u[l+" "+a[0]]||u["* "+a[0]])){!0===s?s=u[i]:!0!==u[i]&&(o=a[0],c.unshift(a[1]));break}if(!0!==s)if(s&&e.throws)t=s(t);else try{t=s(t)}catch(e){return{state:"parsererror",error:s?e:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}(p,w,A,u),u?(p.ifModified&&((x=A.getResponseHeader("Last-Modified"))&&(k.lastModified[i]=x),(x=A.getResponseHeader("etag"))&&(k.etag[i]=x)),204===e||"HEAD"===p.type?C="nocontent":304===e?C="notmodified":(C=w.state,d=w.data,u=!(f=w.error))):(f=C,!e&&C||(C="error",e<0&&(e=0))),A.status=e,A.statusText=(t||C)+"",u?v.resolveWith(g,[d,C,A]):v.rejectWith(g,[A,C,f]),A.statusCode(b),b=void 0,h&&m.trigger(u?"ajaxSuccess":"ajaxError",[A,p,u?d:f]),y.fireWith(g,[A,C]),h&&(m.trigger("ajaxComplete",[A,p]),--k.active||k.event.trigger("ajaxStop")))}return A},getJSON:function(e,t,n){return k.get(e,t,n,"json")},getScript:function(e,t){return k.get(e,void 0,t,"script")}}),k.each(["get","post"],function(e,t){k[t]=function(e,n,r,i){return y(n)&&(i=i||r,r=n,n=void 0),k.ajax(k.extend({url:e,type:t,dataType:i,data:n,success:r},k.isPlainObject(e)&&e))}}),k._evalUrl=function(e){return k.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},k.fn.extend({wrapAll:function(e){var t;return this[0]&&(y(e)&&(e=e.call(this[0])),t=k(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return y(e)?this.each(function(t){k(this).wrapInner(e.call(this,t))}):this.each(function(){var t=k(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=y(e);return this.each(function(n){k(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){k(this).replaceWith(this.childNodes)}),this}}),k.expr.pseudos.hidden=function(e){return!k.expr.pseudos.visible(e)},k.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},k.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(e){}};var Ht={0:200,1223:204},zt=k.ajaxSettings.xhr();v.cors=!!zt&&"withCredentials"in zt,v.ajax=zt=!!zt,k.ajaxTransport(function(e){var t,r;if(v.cors||zt&&!e.crossDomain)return{send:function(i,o){var s,a=e.xhr();if(a.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(s in e.xhrFields)a[s]=e.xhrFields[s];for(s in e.mimeType&&a.overrideMimeType&&a.overrideMimeType(e.mimeType),e.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest"),i)a.setRequestHeader(s,i[s]);t=function(e){return function(){t&&(t=r=a.onload=a.onerror=a.onabort=a.ontimeout=a.onreadystatechange=null,"abort"===e?a.abort():"error"===e?"number"!=typeof a.status?o(0,"error"):o(a.status,a.statusText):o(Ht[a.status]||a.status,a.statusText,"text"!==(a.responseType||"text")||"string"!=typeof a.responseText?{binary:a.response}:{text:a.responseText},a.getAllResponseHeaders()))}},a.onload=t(),r=a.onerror=a.ontimeout=t("error"),void 0!==a.onabort?a.onabort=r:a.onreadystatechange=function(){4===a.readyState&&n.setTimeout(function(){t&&r()})},t=t("abort");try{a.send(e.hasContent&&e.data||null)}catch(e){if(t)throw e}},abort:function(){t&&t()}}}),k.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),k.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return k.globalEval(e),e}}}),k.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),k.ajaxTransport("script",function(e){var t,n;if(e.crossDomain)return{send:function(r,i){t=k("