Stock transactions can now influence forecast in addition to price. Several more minor bug/UI fixes

This commit is contained in:
danielyxie 2019-05-01 15:20:14 -07:00
parent 8726946d4a
commit 585e1ac7aa
18 changed files with 155 additions and 61 deletions

@ -73,6 +73,14 @@ be sold at $98.01, and so on.
This is an important concept to keep in mind if you are trying to purchase/sell a This is an important concept to keep in mind if you are trying to purchase/sell a
large number of shares, as **it can negatively affect your profits**. large number of shares, as **it can negatively affect your profits**.
Transactions Influencing Stock Forecast
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In addition to influencing stock price, buying or selling a large number of shares
of a stock will also influence that stock's "forecast". The forecast is the likelihood
that the stock will increase or decrease in price. The magnitude of this effect depends
on the number of shares being transacted. More shares will have a bigger effect on the
stock forecast.
.. _gameplay_stock_market_order_types: .. _gameplay_stock_market_order_types:
Order Types Order Types

@ -27,6 +27,7 @@ secrets that you've been searching for.
Script Editors <scripteditors> Script Editors <scripteditors>
Game Frozen or Stuck? <gamefrozen> Game Frozen or Stuck? <gamefrozen>
Guides & Tips <guidesandtips> Guides & Tips <guidesandtips>
Tools & Resources <toolsandresources>
Changelog <changelog> Changelog <changelog>
Donate <https://paypal.me/danielyxie> Donate <https://paypal.me/danielyxie>

@ -0,0 +1,23 @@
Tools & Resources
=================
Official Script Repository
--------------------------
There are plans to create an official repository of Bitburner scripts. As of right now,
this is not a priority and has not been started. However, if you'd like
to contribute scripts now, you can find the repository
`here <https://github.com/bitburner-official/bitburner-scripts>`_ and submit pull requests.
Visual Studio Code Extension
----------------------------
One user created a Bitburner extension for the Visual Studio Code (VSCode) editor.
This extension includes several features such as:
* Dynamic RAM calculation
* RAM Usage breakdown
* Typescript definition files with jsdoc comments
* Netscript syntax highlighting
You can find more information and download links
`on this reddit post <https://www.reddit.com/r/Bitburner/comments/bh48y2/visual_studio_code_ram_calculator_extra/>`_.

@ -2105,17 +2105,6 @@ function augmentationExists(name) {
return Augmentations.hasOwnProperty(name); return Augmentations.hasOwnProperty(name);
} }
//Used for testing balance
function giveAllAugmentations() {
for (var name in Augmentations) {
var aug = Augmentations[name];
if (aug == null) {continue;}
var ownedAug = new PlayerOwnedAugmentation(name);
Player.augmentations.push(ownedAug);
}
Player.reapplyAllAugmentations();
}
function displayAugmentationsContent(contentEl) { function displayAugmentationsContent(contentEl) {
removeChildrenFromElement(contentEl); removeChildrenFromElement(contentEl);
contentEl.appendChild(createElement("h1", { contentEl.appendChild(createElement("h1", {

@ -205,6 +205,7 @@ export class CodingContract {
} }
}, },
placeholder: "Enter Solution here", placeholder: "Enter Solution here",
width: "50%",
}) as HTMLInputElement; }) as HTMLInputElement;
solveBtn = createElement("a", { solveBtn = createElement("a", {
class: "a-link-button", class: "a-link-button",

@ -278,7 +278,7 @@ export let CONSTANTS: IMap<any> = {
v0.47.0 v0.47.0
* Stock Market changes: * Stock Market changes:
** Implemented spread. Stock's now have bid and ask prices at which transactions occur ** Implemented spread. Stock's now have bid and ask prices at which transactions occur
** Large transactions will now influence a stock's price. ** Large transactions will now influence a stock's price and forecast
** This "influencing" can take effect in the middle of a transaction ** This "influencing" can take effect in the middle of a transaction
** See documentation for more details on these changes ** See documentation for more details on these changes
** Added getStockAskPrice(), getStockBidPrice() Netscript functions to the TIX API ** Added getStockAskPrice(), getStockBidPrice() Netscript functions to the TIX API
@ -286,12 +286,16 @@ export let CONSTANTS: IMap<any> = {
* Re-sleeves can no longer have the NeuroFlux Governor augmentation * Re-sleeves can no longer have the NeuroFlux Governor augmentation
** This is just a temporary patch until the mechanic gets re-worked ** This is just a temporary patch until the mechanic gets re-worked
* Corporation employees no longer have an "age" stat
* Bug Fix: Corporation employees stats should no longer become negative
* Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios * Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios
* Bug Fix: Coding contracts should no longer generate on the w0r1d_d43m0n server * Bug Fix: Coding contracts should no longer generate on the w0r1d_d43m0n server
* Bug Fix: Duplicate Sleeves now properly have access to all Augmentations if you have a gang * Bug Fix: Duplicate Sleeves now properly have access to all Augmentations if you have a gang
* Bug Fix: getAugmentationsFromFaction() & purchaseAugmentation() functions should now work properly if you have a gang
* Bug Fix: Fixed issue that caused messages (.msg) to be sent when refreshing/reloading the game * Bug Fix: Fixed issue that caused messages (.msg) to be sent when refreshing/reloading the game
* Bug Fix: Purchasing hash upgrades for Bladeburner/Corporation when you don't actually have access to those mechanics no longer gives hashes * Bug Fix: Purchasing hash upgrades for Bladeburner/Corporation when you don't actually have access to those mechanics no longer gives hashes
* Bug Fix: run(), exec(), and spawn() Netscript functions now throw if called with 0 threads * Bug Fix: run(), exec(), and spawn() Netscript functions now throw if called with 0 threads
* Bug Fix: Faction UI should now automatically update reputation
` `
} }

@ -30,10 +30,24 @@ const infoStyleMarkup = {
} }
export class Info extends React.Component<IProps, any> { export class Info extends React.Component<IProps, any> {
render() { constructor(props: IProps) {
super(props);
const formattedRep = numeralWrapper.format(this.props.faction.playerReputation, "0.000a"); this.getFavorGainText = this.getFavorGainText.bind(this);
this.getReputationText = this.getReputationText.bind(this);
}
getFavorGainText(): string {
const favorGain = this.props.faction.getFavorGain()[0]; const favorGain = this.props.faction.getFavorGain()[0];
return `You will earn ${numeralWrapper.format(favorGain, "0,0")} faction favor upon resetting after installing an Augmentation`
}
getReputationText(): string {
const formattedRep = numeralWrapper.format(this.props.faction.playerReputation, "0.000a");
return `Reputation: ${formattedRep}`
}
render() {
const favorTooltip = "Faction favor increases the rate at which you earn reputation for " + const favorTooltip = "Faction favor increases the rate at which you earn reputation for " +
"this faction by 1% per favor. Faction favor is gained whenever you " + "this faction by 1% per favor. Faction favor is gained whenever you " +
"reset after installing an Augmentation. The amount of " + "reset after installing an Augmentation. The amount of " +
@ -51,8 +65,8 @@ export class Info extends React.Component<IProps, any> {
<p style={blockStyleMarkup}>-------------------------</p> <p style={blockStyleMarkup}>-------------------------</p>
<AutoupdatingParagraph <AutoupdatingParagraph
intervalTime={5e3} intervalTime={5e3}
text={`Reputation: ${formattedRep}`} getText={this.getReputationText}
tooltip={`You will earn ${numeralWrapper.format(favorGain, "0,0")} faction favor upon resetting after installing an Augmentation`} getTooltip={this.getFavorGainText}
/> />
<p style={blockStyleMarkup}>-------------------------</p> <p style={blockStyleMarkup}>-------------------------</p>
<ParagraphWithTooltip <ParagraphWithTooltip

@ -158,14 +158,14 @@ export function makeRuntimeRejectMsg(workerScript, msg, exp=null) {
//Run a script from inside a script using run() command //Run a script from inside a script using run() command
export function runScriptFromScript(server, scriptname, args, workerScript, threads=1) { export function runScriptFromScript(server, scriptname, args, workerScript, threads=1) {
//Check if the script is already running //Check if the script is already running
var runningScriptObj = findRunningScript(scriptname, args, server); let runningScriptObj = findRunningScript(scriptname, args, server);
if (runningScriptObj != null) { if (runningScriptObj != null) {
workerScript.scriptRef.log(scriptname + " is already running on " + server.hostname); workerScript.scriptRef.log(scriptname + " is already running on " + server.hostname);
return Promise.resolve(false); return Promise.resolve(false);
} }
//'null/undefined' arguments are not allowed //'null/undefined' arguments are not allowed
for (var i = 0; i < args.length; ++i) { for (let i = 0; i < args.length; ++i) {
if (args[i] == null) { if (args[i] == null) {
workerScript.scriptRef.log("ERROR: Cannot execute a script with null/undefined as an argument"); workerScript.scriptRef.log("ERROR: Cannot execute a script with null/undefined as an argument");
return Promise.resolve(false); return Promise.resolve(false);
@ -191,10 +191,10 @@ export function runScriptFromScript(server, scriptname, args, workerScript, thre
return Promise.resolve(false); return Promise.resolve(false);
} else { } else {
//Able to run script //Able to run script
if(workerScript.disableLogs.ALL == null && workerScript.disableLogs.exec == null && workerScript.disableLogs.run == null && workerScript.disableLogs.spawn == null) { if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.exec == null && workerScript.disableLogs.run == null && workerScript.disableLogs.spawn == null) {
workerScript.scriptRef.log("Running script: " + scriptname + " on " + server.hostname + " with " + threads + " threads and args: " + arrayToString(args) + ". May take a few seconds to start up..."); workerScript.scriptRef.log(`Running script: ${scriptname} on ${server.hostname} with ${threads} threads and args: ${arrayToString(args)}. May take a few seconds to start up...`);
} }
var runningScriptObj = new RunningScript(script, args); let runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads; runningScriptObj.threads = threads;
addWorkerScript(runningScriptObj, server); addWorkerScript(runningScriptObj, server);

@ -3692,17 +3692,26 @@ function NetscriptFunctions(workerScript) {
} }
} }
if (!factionExists(facname)) { const fac = Factions[facname];
if (!(fac instanceof Faction)) {
workerScript.scriptRef.log("ERROR: getAugmentationsFromFaction() failed. Invalid faction name passed in (this is case-sensitive): " + facname); workerScript.scriptRef.log("ERROR: getAugmentationsFromFaction() failed. Invalid faction name passed in (this is case-sensitive): " + facname);
return []; return [];
} }
var fac = Factions[facname]; // If player has a gang with this faction, return all factions
var res = []; if (Player.hasGangWith(facname)) {
for (var i = 0; i < fac.augmentations.length; ++i) { const res = [];
res.push(fac.augmentations[i]); for (const augName in Augmentations) {
const aug = Augmentations[augName];
if (!aug.isSpecial) {
res.push(augName);
}
}
return res;
} }
return res;
return fac.augmentations.slice();
}, },
getAugmentationPrereq : function(name) { getAugmentationPrereq : function(name) {
var ramCost = CONSTANTS.ScriptSingularityFn3RamCost; var ramCost = CONSTANTS.ScriptSingularityFn3RamCost;
@ -3762,50 +3771,58 @@ function NetscriptFunctions(workerScript) {
} }
} }
var fac = Factions[faction]; const fac = Factions[faction];
if (fac == null || !(fac instanceof Faction)) { if (!(fac instanceof Faction)) {
workerScript.scriptRef.log("ERROR: purchaseAugmentation() failed because of invalid faction name: " + faction); workerScript.log("ERROR: purchaseAugmentation() failed because of invalid faction name: " + faction);
return false; return false;
} }
if (!fac.augmentations.includes(name)) { let augs = [];
workerScript.scriptRef.log("ERROR: purchaseAugmentation() failed because the faction " + faction + " does not contain the " + name + " augmentation"); if (Player.hasGangWith(faction)) {
for (const augName in Augmentations) {
const tempAug = Augmentations[augName];
if (!tempAug.isSpecial) {
augs.push(augName);
}
}
} else {
augs = fac.augmentations;
}
if (!augs.includes(name)) {
workerScript.log("ERROR: purchaseAugmentation() failed because the faction " + faction + " does not contain the " + name + " augmentation");
return false; return false;
} }
var aug = Augmentations[name]; const aug = Augmentations[name];
if (aug == null || !(aug instanceof Augmentation)) { if (!(aug instanceof Augmentation)) {
workerScript.scriptRef.log("ERROR: purchaseAugmentation() failed because of invalid augmentation name: " + name); workerScript.log("ERROR: purchaseAugmentation() failed because of invalid augmentation name: " + name);
return false; return false;
} }
var isNeuroflux = false; let isNeuroflux = (aug.name === AugmentationNames.NeuroFluxGovernor);
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
isNeuroflux = true;
}
if (!isNeuroflux) { if (!isNeuroflux) {
for (var j = 0; j < Player.queuedAugmentations.length; ++j) { for (let j = 0; j < Player.queuedAugmentations.length; ++j) {
if (Player.queuedAugmentations[j].name === aug.name) { if (Player.queuedAugmentations[j].name === aug.name) {
workerScript.scriptRef.log("ERROR: purchaseAugmentation() failed because you already have " + name); workerScript.log("ERROR: purchaseAugmentation() failed because you already have " + name);
return false; return false;
} }
} }
for (var j = 0; j < Player.augmentations.length; ++j) { for (let j = 0; j < Player.augmentations.length; ++j) {
if (Player.augmentations[j].name === aug.name) { if (Player.augmentations[j].name === aug.name) {
workerScript.scriptRef.log("ERROR: purchaseAugmentation() failed because you already have " + name); workerScript.log("ERROR: purchaseAugmentation() failed because you already have " + name);
return false; return false;
} }
} }
} }
if (fac.playerReputation < aug.baseRepRequirement) { if (fac.playerReputation < aug.baseRepRequirement) {
workerScript.scriptRef.log("ERROR: purchaseAugmentation() failed because you do not have enough reputation with " + fac.name); workerScript.log("ERROR: purchaseAugmentation() failed because you do not have enough reputation with " + fac.name);
return false; return false;
} }
var res = purchaseAugmentation(aug, fac, true); var res = purchaseAugmentation(aug, fac, true);
workerScript.scriptRef.log(res); workerScript.log(res);
if (isString(res) && res.startsWith("You purchased")) { if (isString(res) && res.startsWith("You purchased")) {
Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain);
return true; return true;

@ -136,6 +136,7 @@ export interface IPlayer {
getUpgradeHomeRamCost(): number; getUpgradeHomeRamCost(): number;
gotoLocation(to: LocationName): boolean; gotoLocation(to: LocationName): boolean;
hasCorporation(): boolean; hasCorporation(): boolean;
hasGangWith(facName: string): boolean;
hasTorRouter(): boolean; hasTorRouter(): boolean;
inBladeburner(): boolean; inBladeburner(): boolean;
inGang(): boolean; inGang(): boolean;

@ -23,7 +23,11 @@ export function getGangFaction() {
} }
export function getGangName() { export function getGangName() {
return this.gang.facName; return this.inGang() ? this.gang.facName : "";
}
export function hasGangWith(facName) {
return this.inGang() && this.gang.facName === facName;
} }
export function inGang() { export function inGang() {

@ -424,7 +424,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) {
//designated server, and false otherwise //designated server, and false otherwise
export function findRunningScript(filename, args, server) { export function findRunningScript(filename, args, server) {
for (var i = 0; i < server.runningScripts.length; ++i) { for (var i = 0; i < server.runningScripts.length; ++i) {
if (server.runningScripts[i].filename == filename && if (server.runningScripts[i].filename === filename &&
compareArrays(server.runningScripts[i].args, args)) { compareArrays(server.runningScripts[i].args, args)) {
return server.runningScripts[i]; return server.runningScripts[i];
} }

@ -206,6 +206,7 @@ export function stockMarketCycle() {
if (stock.b) { thresh = 0.4; } if (stock.b) { thresh = 0.4; }
if (Math.random() < thresh) { if (Math.random() < thresh) {
stock.b = !stock.b; stock.b = !stock.b;
if (stock.otlkMag < 10) { stock.otlkMag += 0.4; }
} }
} }
} }

@ -2,6 +2,9 @@ import { Stock } from "./Stock";
import { PositionTypes } from "./data/PositionTypes"; import { PositionTypes } from "./data/PositionTypes";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
// Amount by which a stock's forecast changes during each price movement
const forecastChangePerPriceMovement = 0.4;
/** /**
* Given a stock, calculates the amount by which the stock price is multiplied * Given a stock, calculates the amount by which the stock price is multiplied
* for an 'upward' price movement. This does not actually increase the stock's price, * for an 'upward' price movement. This does not actually increase the stock's price,
@ -86,7 +89,7 @@ export function getBuyTransactionCost(stock: Stock, shares: number, posType: Pos
} }
/** /**
* Processes a buy transaction's resulting price movement. * Processes a buy transaction's resulting price AND forecast movement.
* @param {Stock} stock - Stock being purchased * @param {Stock} stock - Stock being purchased
* @param {number} shares - Number of shares being transacted * @param {number} shares - Number of shares being transacted
* @param {PositionTypes} posType - Long or short position * @param {PositionTypes} posType - Long or short position
@ -125,6 +128,10 @@ export function processBuyTransactionPriceMovement(stock: Stock, shares: number,
stock.price = currPrice; stock.price = currPrice;
stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement); stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
// Forecast always decreases in magnitude
const forecastChange = Math.min(5, forecastChangePerPriceMovement * numIterations);
stock.otlkMag -= forecastChange;
} }
/** /**
@ -228,6 +235,10 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
stock.price = currPrice; stock.price = currPrice;
stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement); stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
// Forecast always decreases in magnitude
const forecastChange = Math.min(5, forecastChangePerPriceMovement * numIterations);
stock.otlkMag -= forecastChange;
} }
/** /**

@ -25,7 +25,7 @@ export class StockTickerPositionText extends React.Component<IProps, any> {
// Caculate total returns // Caculate total returns
const totalCost = stock.playerShares * stock.playerAvgPx; const totalCost = stock.playerShares * stock.playerAvgPx;
const gains = (stock.price - stock.playerAvgPx) * stock.playerShares; const gains = (stock.getBidPrice() - stock.playerAvgPx) * stock.playerShares;
let percentageGains = gains / totalCost; let percentageGains = gains / totalCost;
if (isNaN(percentageGains)) { percentageGains = 0; } if (isNaN(percentageGains)) { percentageGains = 0; }
@ -56,7 +56,7 @@ export class StockTickerPositionText extends React.Component<IProps, any> {
// Caculate total returns // Caculate total returns
const totalCost = stock.playerShortShares * stock.playerAvgShortPx; const totalCost = stock.playerShortShares * stock.playerAvgShortPx;
const gains = (stock.playerAvgShortPx - stock.price) * stock.playerShortShares; const gains = (stock.playerAvgShortPx - stock.getAskPrice()) * stock.playerShortShares;
let percentageGains = gains / totalCost; let percentageGains = gains / totalCost;
if (isNaN(percentageGains)) { percentageGains = 0; } if (isNaN(percentageGains)) { percentageGains = 0; }

@ -8,8 +8,8 @@ import * as React from "react";
interface IProps { interface IProps {
intervalTime?: number; intervalTime?: number;
style?: object; style?: object;
text: string; getText: () => string;
tooltip?: string; getTooltip?: () => string;
} }
interface IState { interface IState {
@ -49,7 +49,14 @@ export class AutoupdatingParagraph extends React.Component<IProps, IState> {
} }
render() { render() {
const hasTooltip = this.props.tooltip != null && this.props.tooltip !== ""; let hasTooltip = this.props.getTooltip != null;
let tooltip: string | null;
if (hasTooltip) {
tooltip = this.props.getTooltip!();
if (tooltip === "") {
hasTooltip = false;
}
}
const className = "tooltip"; const className = "tooltip";
@ -57,13 +64,13 @@ export class AutoupdatingParagraph extends React.Component<IProps, IState> {
let tooltipMarkup: IInnerHTMLMarkup | null; let tooltipMarkup: IInnerHTMLMarkup | null;
if (hasTooltip) { if (hasTooltip) {
tooltipMarkup = { tooltipMarkup = {
__html: this.props.tooltip! __html: tooltip!
} }
} }
return ( return (
<p className={className} style={this.props.style}> <p className={className} style={this.props.style}>
{this.props.text} {this.props.getText()}
{ {
hasTooltip && hasTooltip &&
<span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup!}></span> <span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup!}></span>

@ -9,11 +9,13 @@ export function arrayToString<T>(a: T[]) {
const vals: any[] = []; const vals: any[] = [];
for (let i = 0; i < a.length; ++i) { for (let i = 0; i < a.length; ++i) {
let elem: any = a[i]; let elem: any = a[i];
if (typeof elem === "string") { if (Array.isArray(elem)) {
elem = arrayToString(elem);
} else if (typeof elem === "string") {
elem = `"${elem}"`; elem = `"${elem}"`;
} }
vals.push(elem); vals.push(elem);
} }
return `[${vals.join(", ")}]`; return `[${vals.join(", ")}]`;
} }

@ -9,8 +9,19 @@ export function compareArrays<T>(a1: T[], a2: T[]) {
return false; return false;
} }
for (let i: number = 0; i < a1.length; ++i) { for (let i = 0; i < a1.length; ++i) {
if (a1[i] !== a2[i]) { if (Array.isArray(a1[i])) {
// If the other element is not an array, then these cannot be equal
if (!Array.isArray(a2[i])) {
return false;
}
const elem1 = <any[]><any>a1[i];
const elem2 = <any[]><any>a2[i];
if (!compareArrays(elem1, elem2)) {
return false;
}
} else if (a1[i] !== a2[i]) {
return false; return false;
} }
} }