diff --git a/doc/source/netscriptixapi.rst b/doc/source/netscriptixapi.rst index f621a0e07..cf50c6b62 100644 --- a/doc/source/netscriptixapi.rst +++ b/doc/source/netscriptixapi.rst @@ -2,7 +2,7 @@ Netscript Trade Information eXchange (TIX) API ============================================== The Trade Information eXchange (TIX) is the communications protocol supported by the World Stock Exchange (WSE). -The WESE provides an API that allows you to automatically communicate with the +The WSE provides an API that allows you to automatically communicate with the `Stock Market `_. This API lets you write code using Netscript to build automated trading systems and create your own algorithmic trading strategies. Access to this TIX API can be purchased by visiting the World Stock Exchange in-game. @@ -11,7 +11,7 @@ Access to the TIX API currently costs $5 billion. After you purchase it, you wil access even after you 'reset' by installing Augmentations getStockSymbols -------------- +--------------- .. js:function:: getStockSymbols() @@ -214,3 +214,23 @@ getStockForecast In other words, if this function returned 0.30 for a stock, then this means that the stock's price has a 30% chance of increasing and a 70% chance of decreasing during the next tick. + +purchase4SMarketData +-------------------- + +.. js:function:: purchase4SMarketData() + + Purchase 4S Market Data Access. + + Returns true if you successfully purchased it or if you already have access. + Returns false otherwise. + +purchase4SMarketDataTixApi +-------------------------- + +.. js:function:: purchase4SMarketDataTixApi() + + Purchase 4S Market Data TIX API Access. + + Returns true if you successfully purchased it or if you already have access. + Returns false otherwise. diff --git a/netscript.js b/netscript.js index 31996d179..362b7105a 100644 --- a/netscript.js +++ b/netscript.js @@ -95,6 +95,7 @@ let NetscriptFunctions = "getStockPrice|getStockPosition|getStockSymbols|buyStock|sellStock|" + "shortStock|sellShort|" + "placeOrder|cancelOrder|getStockVolatility|getStockForecast|" + + "purchase4SMarketData|purchase4SMarketDataTixApi|" + // Hacknet Node API "hacknet|numNodes|purchaseNode|getPurchaseNodeCost|getNodeStats|" + diff --git a/src/Bladeburner.js b/src/Bladeburner.js index 9483cf9b3..8874ebe7b 100644 --- a/src/Bladeburner.js +++ b/src/Bladeburner.js @@ -714,6 +714,7 @@ function Bladeburner(params={}) { //Console command history this.consoleHistory = []; + this.consoleLogs = []; //Initialization initBladeburner(); @@ -867,6 +868,12 @@ Bladeburner.prototype.process = function() { this.resetAction(); } + // If the Player has no Stamina, set action to idle + if (this.stamina <= 0) { + this.log("Your Bladeburner action was cancelled because your stamina hit 0"); + this.resetAction(); + } + //A 'tick' for this mechanic is one second (= 5 game cycles) if (this.storedCycles >= CyclesPerSecond) { var seconds = Math.floor(this.storedCycles / CyclesPerSecond); @@ -1348,7 +1355,6 @@ Bladeburner.prototype.completeAction = function() { Player.gainIntelligenceExp(BaseIntGain); Player.gainCharismaExp(charismaExpGain); this.changeRank(0.1 * BitNodeMultipliers.BladeburnerRank); - console.log("DEBUG: Field Analysis effectiveness is " + (eff * this.skillMultipliers.successChanceEstimate)); this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate); if (this.logging.general) { this.log("Field analysis completed. Gained 0.1 rank, " + formatNumber(hackingExpGain, 1) + " hacking exp, and " + formatNumber(charismaExpGain, 1) + " charisma exp"); @@ -1741,11 +1747,15 @@ Bladeburner.prototype.createContent = function() { document.getElementById("entire-game-container").appendChild(DomElems.bladeburnerDiv); - this.postToConsole("Bladeburner Console BETA"); - this.postToConsole("Type 'help' to see console commands"); - for (let i = 0; i < this.consoleHistory.length; ++i) { - this.postToConsole(this.consoleHistory[i]); + if (this.consoleLogs.length === 0) { + this.postToConsole("Bladeburner Console BETA"); + this.postToConsole("Type 'help' to see console commands"); + } else { + for (let i = 0; i < this.consoleLogs.length; ++i) { + this.postToConsole(this.consoleLogs[i], false); + } } + DomElems.consoleInput.focus(); } @@ -2738,12 +2748,22 @@ Bladeburner.prototype.updateSkillsUIElement = function(el, skill) { } //Bladeburner Console Window -Bladeburner.prototype.postToConsole = function(input) { +Bladeburner.prototype.postToConsole = function(input, saveToLogs=true) { + const MaxConsoleEntries = 100; + if (saveToLogs === true) { + this.consoleLogs.push(input); + if (this.consoleLogs.length > MaxConsoleEntries) { + this.consoleLogs.shift(); + } + } + if (input == null || DomElems.consoleDiv == null) {return;} $("#bladeubrner-console-input-row").before('' + input + ''); - if (DomElems.consoleTable.childNodes.length > 200) { + + if (DomElems.consoleTable.childNodes.length > MaxConsoleEntries) { DomElems.consoleTable.removeChild(DomElems.consoleTable.firstChild); } + this.updateConsoleScroll(); } @@ -2759,6 +2779,8 @@ Bladeburner.prototype.clearConsole = function() { while (DomElems.consoleTable.childNodes.length > 1) { DomElems.consoleTable.removeChild(DomElems.consoleTable.firstChild); } + + this.consoleLogs.length = 0; } Bladeburner.prototype.log = function(input) { diff --git a/src/Constants.js b/src/Constants.js index 7a49fbeff..b3f971fae 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -505,6 +505,7 @@ let CONSTANTS = { v0.41.1 * IMPORTANT - Netscript Changes: ** purchaseTor() now returns true if you already have a TOR router (it used to return false) + ** getPurchasedServerCost() now returns Infinity if the specified RAM is an invalid amount or is greater than the max amount of RAM (2 ^ 20 GB) ** Added purchase4SMarketData() and purchase4SMarketDataTixApi() functions * Stock Market changes: @@ -518,9 +519,11 @@ let CONSTANTS = { * Decreased the Hacking Level multiplier for BitNodes 6 and 7 to 0.4 (from 0.5) * Bladeburner console history is now saved and persists when switching screens or closing/reopening the game + * In Bladeburner, if your stamina reaches 0 your current action will be cancelled * b1t_flum3.exe is no longer removed from your home computer upon reset - * Added main menu link for the Stock Market + * Added main menu link for the Stock Market (once you've purchased an account) * Job main menu link only appears if you actually have a job + * Bug Fix: After installing Augs, the "Portfolio Mode" button on the Stock Market page should be properly reset ` } diff --git a/src/DevMenu.js b/src/DevMenu.js index 2f1e7405b..ce2c31cd1 100644 --- a/src/DevMenu.js +++ b/src/DevMenu.js @@ -5,7 +5,12 @@ import {Factions} from "./Faction"; import {Player} from "./Player"; import {AllServers} from "./Server"; import {hackWorldDaemon} from "./RedPill"; +import {StockMarket, + SymbolToStockMap} from "./StockMarket"; +import {Stock} from "./Stock"; import {Terminal} from "./Terminal"; +import {numeralWrapper} from "./ui/numeralFormat"; +import {dialogBoxCreate} from "../utils/DialogBox"; import {exceptionAlert} from "../utils/helpers/exceptionAlert"; import {createElement} from "../utils/uiHelpers/createElement"; import {removeElementById} from "../utils/uiHelpers/removeElementById"; @@ -322,6 +327,7 @@ export function createDevMenu() { const bladeburnerGainRankInput = createElement("input", { class: "text-input", + margin: "5px", placeholder: "Rank to gain (or negative to lose rank)", type: "number", }); @@ -344,6 +350,7 @@ export function createDevMenu() { const gangStoredCyclesInput = createElement("input", { class: "text-input", + margin: "5px", placeholder: "# Cycles to add", type: "number", }); @@ -372,6 +379,73 @@ export function createDevMenu() { innerText: "Generate Random Contract", }); + // Stock Market + const stockmarketHeader = createElement("h2", {innerText: "Stock Market"}); + + const stockInput = createElement("input", { + class: "text-input", + display: "block", + placeholder: "Stock symbol(s), or 'all'", + }); + + function processStocks(cb) { + const input = stockInput.value.toString().replace(/\s/g, ''); + + // Empty input, or "all", will process all stocks + if (input === "" || input.toLowerCase() === "all") { + for (const name in StockMarket) { + if (StockMarket.hasOwnProperty(name)) { + const stock = StockMarket[name]; + if (stock instanceof Stock) { + cb(stock); + } + } + } + return; + } + + const stockSymbols = input.split(","); + for (let i = 0; i < stockSymbols.length; ++i) { + const stock = SymbolToStockMap[stockSymbols]; + if (stock instanceof Stock) { + cb(stock); + } + } + } + + const stockPriceChangeInput = createElement("input", { + class: "text-input", + margin: "5px", + placeholder: "Price to change stock(s) to", + type: "number", + }); + + const stockPriceChangeBtn = createElement("button", { + class: "std-button", + clickListener: () => { + const price = parseInt(stockPriceChangeInput.value); + if (isNaN(price)) { return; } + + processStocks((stock) => { + stock.price = price; + }); + dialogBoxCreate(`Stock Prices changed to ${price}`); + }, + innerText: "Change Stock Price(s)", + }); + + const stockViewPriceCapBtn = createElement("button", { + class: "std-button", + clickListener: () => { + let text = ""; + processStocks((stock) => { + text += `${stock.symbol}: ${numeralWrapper.format(stock.cap, '$0.000a')}
`; + }); + dialogBoxCreate(text); + }, + innerText: "View Stock Price Caps", + }); + // Add everything to container, then append to main menu const devMenuContainer = createElement("div", { class: "generic-menupage-container", @@ -433,6 +507,12 @@ export function createDevMenu() { devMenuContainer.appendChild(createElement("br")); devMenuContainer.appendChild(contractsHeader); devMenuContainer.appendChild(generateRandomContractBtn); + devMenuContainer.appendChild(stockmarketHeader); + devMenuContainer.appendChild(stockInput); + devMenuContainer.appendChild(stockPriceChangeInput); + devMenuContainer.appendChild(stockPriceChangeBtn); + devMenuContainer.appendChild(createElement("br")); + devMenuContainer.appendChild(stockViewPriceCapBtn); const entireGameContainer = document.getElementById("entire-game-container"); if (entireGameContainer == null) { diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index fdc0b969c..2e9e6ccf6 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -1715,7 +1715,7 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use purchase4SMarketData()"); } - if (Player.Player.has4SData) { + if (Player.has4SData) { if (workerScript.shouldLog("purchase4SMarketData")) { workerScript.log("Already purchased 4S Market Data"); } @@ -1794,7 +1794,7 @@ function NetscriptFunctions(workerScript) { cost = getPurchaseServerRamCostGuard(ram); } catch (e) { workerScript.scriptRef.log("ERROR: 'getPurchasedServerCost()' " + e.message); - return ""; + return Infinity; } return cost; diff --git a/src/StockMarket.js b/src/StockMarket.js index 22213d55a..23b2fed0a 100755 --- a/src/StockMarket.js +++ b/src/StockMarket.js @@ -547,7 +547,7 @@ function sellShort(stock, shares, workerScript=null) { } function processStockPrices(numCycles=1) { - if (isNaN(StockMarket.storedCycles)) { StockMarket.storedCycles = 0; } + if (StockMarket.storedCycles == null || isNaN(StockMarket.storedCycles)) { StockMarket.storedCycles = 0; } StockMarket.storedCycles += numCycles; // Stock Prices updated every 6 seconds on average. But if there are stored @@ -572,10 +572,10 @@ function processStockPrices(numCycles=1) { var chc = 50; if (stock.b) { - chc = (chc + stock.otlkMag)/100; + chc = (chc + stock.otlkMag) / 100; if (isNaN(chc)) {chc = 0.5;} } else { - chc = (chc - stock.otlkMag)/100; + chc = (chc - stock.otlkMag) / 100; if (isNaN(chc)) {chc = 0.5;} } if (stock.price >= stock.cap) { @@ -895,6 +895,7 @@ function displayStockMarketContent() { //Switch to Portfolio Mode Button if (modeBtn) { + stockMarketPortfolioMode = false; modeBtn.innerHTML = "Switch to 'Portfolio' Mode" + "Displays only the stocks for which you have shares or orders"; modeBtn.addEventListener("click", switchToPortfolioMode); diff --git a/src/engine.js b/src/engine.js index 19a2f165b..37e031d2e 100644 --- a/src/engine.js +++ b/src/engine.js @@ -1246,6 +1246,11 @@ const Engine = { //Passive faction rep gain offline processPassiveFactionRepGain(numCyclesOffline); + // Stock Market offline progress + if (Player.hasWseAccount) { + processStockPrices(numCyclesOffline); + } + //Gang progress for BitNode 2 if (Player.bitNodeN != null && Player.bitNodeN === 2 && Player.inGang()) { Player.gang.process(numCyclesOffline, Player);