Refactored stock buying/selling code into its own file. Refactored WorkerScript & NetscriptEnvironment into their own Typescript classes. Refactored a ton of code to remove circular dependencies

This commit is contained in:
danielyxie 2019-05-04 21:03:40 -07:00
parent 585e1ac7aa
commit 8a5b6f6cbc
47 changed files with 2867 additions and 2773 deletions

@ -73,6 +73,9 @@ 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
large number of shares, as **it can negatively affect your profits**.
.. note:: On the Stock Market UI, the displayed profit of a position does not take
this "influencing" into account.
Transactions Influencing Stock Forecast
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In addition to influencing stock price, buying or selling a large number of shares
@ -131,7 +134,36 @@ 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.
.. note:: Limit and Stop orders do **not** take into account the fact that
transactions can influence a stock's price. If a stock's price
changes mid-transaction, a limit/stop order will continue to execute
even if its conditions are no longer met.
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.
Under the Hood
--------------
Stock prices are updated very ~6 seconds.
Whether a stock's price moves up or down is determined by RNG. However,
stocks have properties that can influence the way their price moves. These properties
are hidden, although some of them can be made visible by purchasing the
Four Sigma (4S) Market Data upgrade. Some examples of these properties are:
* Volatility
* Likelihood of increasing or decreasing
* How easily a stock's price/forecast is influenced by transactions
* Spread percentage
* Maximum price (not a real maximum, more of a "soft cap")
Each stock has its own unique values for these properties.
Offline Progression
-------------------
The Stock Market does not change or process anything while the game has closed.
However, it does accumulate time when offline. This accumulated time allows
the stock market to run 50% faster when the game is opened again. This means
that stock prices will update every ~4 seconds instead of 6.

@ -7,7 +7,6 @@ import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"
import { CONSTANTS } from "../Constants";
import { Factions,
factionExists } from "../Faction/Factions";
import { hasBladeburnerSF } from "../NetscriptFunctions";
import { addWorkerScript } from "../NetscriptWorker";
import { Player } from "../Player";
import { prestigeAugmentation } from "../Prestige";
@ -2094,7 +2093,7 @@ function installAugmentations(cbScript=null) {
}
var runningScriptObj = new RunningScript(script, []); //No args
runningScriptObj.threads = 1; //Only 1 thread
home.runScript(runningScriptObj, Player);
home.runScript(runningScriptObj, Player.hacknet_node_money_mult);
addWorkerScript(runningScriptObj, home);
}
}

@ -38,60 +38,6 @@ export let CONSTANTS: IMap<any> = {
// NeuroFlux Governor Augmentation cost multiplier
NeuroFluxGovernorLevelMult: 1.14,
// RAM Costs for Netscript functions
ScriptBaseRamCost: 1.6,
ScriptDomRamCost: 25,
ScriptWhileRamCost: 0,
ScriptForRamCost: 0,
ScriptIfRamCost: 0,
ScriptHackRamCost: 0.1,
ScriptHackAnalyzeRamCost: 1,
ScriptGrowRamCost: 0.15,
ScriptGrowthAnalyzeRamCost: 1,
ScriptWeakenRamCost: 0.15,
ScriptScanRamCost: 0.2,
ScriptPortProgramRamCost: 0.05,
ScriptRunRamCost: 1.0,
ScriptExecRamCost: 1.3,
ScriptSpawnRamCost: 2.0,
ScriptScpRamCost: 0.6,
ScriptKillRamCost: 0.5,
ScriptHasRootAccessRamCost: 0.05,
ScriptGetHostnameRamCost: 0.05,
ScriptGetHackingLevelRamCost: 0.05,
ScriptGetMultipliersRamCost: 4.0,
ScriptGetServerRamCost: 0.1,
ScriptFileExistsRamCost: 0.1,
ScriptIsRunningRamCost: 0.1,
ScriptHacknetNodesRamCost: 4.0,
ScriptHNUpgLevelRamCost: 0.4,
ScriptHNUpgRamRamCost: 0.6,
ScriptHNUpgCoreRamCost: 0.8,
ScriptGetStockRamCost: 2.0,
ScriptBuySellStockRamCost: 2.5,
ScriptGetPurchaseServerRamCost: 0.25,
ScriptPurchaseServerRamCost: 2.25,
ScriptGetPurchasedServerLimit: 0.05,
ScriptGetPurchasedServerMaxRam: 0.05,
ScriptRoundRamCost: 0.05,
ScriptReadWriteRamCost: 1.0,
ScriptArbScriptRamCost: 1.0,
ScriptGetScriptRamCost: 0.1,
ScriptGetHackTimeRamCost: 0.05,
ScriptGetFavorToDonate: 0.10,
ScriptCodingContractBaseRamCost: 10,
ScriptSleeveBaseRamCost: 4,
ScriptSingularityFn1RamCost: 1,
ScriptSingularityFn2RamCost: 2,
ScriptSingularityFn3RamCost: 3,
ScriptSingularityFnRamMult: 2, // Multiplier for RAM cost outside of BN-4
ScriptGangApiBaseRamCost: 4,
ScriptBladeburnerApiBaseRamCost: 4,
NumNetscriptPorts: 20,
// Server-related constants
@ -287,6 +233,8 @@ export let CONSTANTS: IMap<any> = {
* Re-sleeves can no longer have the NeuroFlux Governor augmentation
** This is just a temporary patch until the mechanic gets re-worked
* Adjusted RAM costs of Netscript Singularity functions (mostly increased)
* Netscript Singularity functions no longer cost extra RAM outside of BitNode-4
* 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

@ -6,34 +6,35 @@
*/
import { IReturnStatus } from "../types";
import { HacknetServer } from "../Hacknet/HacknetServer";
//import { HacknetServer } from "../Hacknet/HacknetServer";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Server } from "../Server/Server";
function baseCheck(server: Server | HacknetServer, fnName: string): IReturnStatus {
if (server instanceof HacknetServer) {
function baseCheck(server: Server, fnName: string): IReturnStatus {
const hostname = server.hostname;
if (!("requiredHackingSkill" in server)) {
return {
res: false,
msg: `Cannot ${fnName} ${server.hostname} server because it is a Hacknet Node`
msg: `Cannot ${fnName} ${hostname} server because it is a Hacknet Node`
}
}
if (server.hasAdminRights === false) {
return {
res: false,
msg: `Cannot ${fnName} ${server.hostname} server because you do not have root access`,
msg: `Cannot ${fnName} ${hostname} server because you do not have root access`,
}
}
return { res: true }
}
export function netscriptCanHack(server: Server | HacknetServer, p: IPlayer): IReturnStatus {
export function netscriptCanHack(server: Server, p: IPlayer): IReturnStatus {
const initialCheck = baseCheck(server, "hack");
if (!initialCheck.res) { return initialCheck; }
let s = <Server>server;
let s = server;
if (s.requiredHackingSkill > p.hacking_skill) {
return {
res: false,
@ -44,10 +45,10 @@ export function netscriptCanHack(server: Server | HacknetServer, p: IPlayer): IR
return { res: true }
}
export function netscriptCanGrow(server: Server | HacknetServer): IReturnStatus {
export function netscriptCanGrow(server: Server): IReturnStatus {
return baseCheck(server, "grow");
}
export function netscriptCanWeaken(server: Server | HacknetServer): IReturnStatus {
export function netscriptCanWeaken(server: Server): IReturnStatus {
return baseCheck(server, "weaken");
}

@ -1,26 +1,32 @@
import { HacknetNode,
import {
HacknetNode,
BaseCostForHacknetNode,
HacknetNodePurchaseNextMult,
HacknetNodeMaxLevel,
HacknetNodeMaxRam,
HacknetNodeMaxCores } from "./HacknetNode";
import { HacknetServer,
HacknetNodeMaxCores
} from "./HacknetNode";
import {
HacknetServer,
BaseCostForHacknetServer,
HacknetServerPurchaseMult,
HacknetServerMaxLevel,
HacknetServerMaxRam,
HacknetServerMaxCores,
HacknetServerMaxCache,
MaxNumberHacknetServers } from "./HacknetServer";
MaxNumberHacknetServers
} from "./HacknetServer";
import { HashManager } from "./HashManager";
import { HashUpgrades } from "./HashUpgrades";
import { generateRandomContract } from "../CodingContractGenerator";
import { iTutorialSteps, iTutorialNextStep,
ITutorial} from "../InteractiveTutorial";
import {
iTutorialSteps,
iTutorialNextStep,
ITutorial
} from "../InteractiveTutorial";
import { Player } from "../Player";
import { AddToAllServers,
AllServers } from "../Server/AllServers";
import { AddToAllServers, AllServers } from "../Server/AllServers";
import { GetServerByHostname } from "../Server/ServerHelpers";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { Page, routing } from "../ui/navigationTracking";
@ -66,7 +72,7 @@ export function purchaseHacknet() {
if (!Player.canAfford(cost)) { return -1; }
Player.loseMoney(cost);
const server = Player.createHacknetServer();
Player.hashManager.updateCapacity(Player);
Player.hashManager.updateCapacity(Player.hacknetNodes);
return numOwned;
} else {
@ -79,8 +85,7 @@ export function purchaseHacknet() {
// Auto generate a name for the Node
const name = "hacknet-node-" + numOwned;
const node = new HacknetNode(name);
node.updateMoneyGainRate(Player);
const node = new HacknetNode(name, Player.hacknet_node_money_mult);
Player.loseMoney(cost);
Player.hacknetNodes.push(node);
@ -116,26 +121,26 @@ export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) {
throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`);
}
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1, Player))) {
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1, Player.hacknet_node_level_cost_mult))) {
return 0;
}
let min = 1;
let max = maxLevel - 1;
let levelsToMax = maxLevel - nodeObj.level;
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, Player))) {
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, Player.hacknet_node_level_cost_mult))) {
return levelsToMax;
}
while (min <= max) {
var curr = (min + max) / 2 | 0;
if (curr !== maxLevel &&
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player)) &&
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player))) {
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult)) &&
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player.hacknet_node_level_cost_mult))) {
return Math.min(levelsToMax, curr);
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult))) {
max = curr - 1;
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult))) {
min = curr + 1;
} else {
return Math.min(levelsToMax, curr);
@ -149,7 +154,7 @@ export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`);
}
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1, Player))) {
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1, Player.hacknet_node_ram_cost_mult))) {
return 0;
}
@ -159,13 +164,13 @@ export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
} else {
levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram));
}
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, Player))) {
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, Player.hacknet_node_ram_cost_mult))) {
return levelsToMax;
}
//We'll just loop until we find the max
for (let i = levelsToMax-1; i >= 0; --i) {
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i, Player))) {
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i, Player.hacknet_node_ram_cost_mult))) {
return i;
}
}
@ -177,14 +182,14 @@ export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`);
}
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1, Player))) {
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1, Player.hacknet_node_core_cost_mult))) {
return 0;
}
let min = 1;
let max = maxLevel - 1;
const levelsToMax = maxLevel - nodeObj.cores;
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, Player))) {
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, Player.hacknet_node_core_cost_mult))) {
return levelsToMax;
}
@ -192,12 +197,12 @@ export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
while (min <= max) {
let curr = (min + max) / 2 | 0;
if (curr != maxLevel &&
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player)) &&
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player))) {
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult)) &&
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player.hacknet_node_core_cost_mult))) {
return Math.min(levelsToMax, curr);
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult))) {
max = curr - 1;
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult))) {
min = curr + 1;
} else {
return Math.min(levelsToMax, curr);
@ -242,7 +247,134 @@ export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) {
return 0;
}
// Initial construction of Hacknet Nodes UI
export function purchaseLevelUpgrade(node, levels=1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateLevelUpgradeCost(sanitizedLevels, Player.hacknet_node_level_cost_mult);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
const isServer = (node instanceof HacknetServer);
// If we're at max level, return false
if (node.level >= (isServer ? HacknetServerMaxLevel : HacknetNodeMaxLevel)) {
return false;
}
// If the number of specified upgrades would exceed the max level, calculate
// the maximum number of upgrades and use that
if (node.level + sanitizedLevels > (isServer ? HacknetServerMaxLevel : HacknetNodeMaxLevel)) {
const diff = Math.max(0, (isServer ? HacknetServerMaxLevel : HacknetNodeMaxLevel) - node.level);
return purchaseLevelUpgrade(node, diff);
}
if (!Player.canAfford(cost)) {
return false;
}
Player.loseMoney(cost);
node.upgradeLevel(sanitizedLevels, Player.hacknet_node_money_mult);
return true;
}
export function purchaseRamUpgrade(node, levels=1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateRamUpgradeCost(sanitizedLevels, Player.hacknet_node_ram_cost_mult);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
const isServer = (node instanceof HacknetServer);
// Fail if we're already at max
if (node.ram >= (isServer ? HacknetServerMaxRam : HacknetNodeMaxRam)) {
return false;
}
// If the number of specified upgrades would exceed the max RAM, calculate the
// max possible number of upgrades and use that
if (isServer) {
if (node.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerMaxRam) {
const diff = Math.max(0, Math.log2(Math.round(HacknetServerMaxRam / node.maxRam)));
return purchaseRamUpgrade(node, diff);
}
} else {
if (node.ram * Math.pow(2, sanitizedLevels) > HacknetNodeMaxRam) {
const diff = Math.max(0, Math.log2(Math.round(HacknetNodeMaxRam / node.ram)));
return purchaseRamUpgrade(node, diff);
}
}
if (!Player.canAfford(cost)) {
return false;
}
Player.loseMoney(cost);
node.upgradeRam(sanitizedLevels, Player.hacknet_node_money_mult);
return true;
}
export function purchaseCoreUpgrade(node, levels=1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateCoreUpgradeCost(sanitizedLevels, Player.hacknet_node_core_cost_mult);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
const isServer = (node instanceof HacknetServer);
// Fail if we're already at max
if (node.cores >= (isServer ? HacknetServerMaxCores : HacknetNodeMaxCores)) {
return false;
}
// If the specified number of upgrades would exceed the max Cores, calculate
// the max possible number of upgrades and use that
if (node.cores + sanitizedLevels > (isServer ? HacknetServerMaxCores : HacknetNodeMaxCores)) {
const diff = Math.max(0, (isServer ? HacknetServerMaxCores : HacknetNodeMaxCores) - node.cores);
return purchaseCoreUpgrade(node, diff);
}
if (!Player.canAfford(cost)) {
return false;
}
Player.loseMoney(cost);
node.upgradeCore(sanitizedLevels, Player.hacknet_node_money_mult);
return true;
}
export function purchaseCacheUpgrade(node, levels=1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateCoreUpgradeCost(sanitizedLevels);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
if (!(node instanceof HacknetServer)) {
console.warn(`purchaseCacheUpgrade() called for a non-HacknetNode`);
return false;
}
// Fail if we're already at max
if (node.cache + sanitizedLevels > HacknetServerMaxCache) {
const diff = Math.max(0, HacknetServerMaxCache - node.cache);
return purchaseCacheUpgrade(node, diff);
}
if (!Player.canAfford(cost)) {
return false;
}
Player.loseMoney(cost);
node.upgradeCache(sanitizedLevels);
}
// Create/Refresh Hacknet Nodes UI
export function renderHacknetNodesUI() {
if (!routing.isOn(Page.HacknetNodes)) { return; }
@ -303,6 +435,37 @@ function processAllHacknetServerEarnings(numCycles) {
return hashes;
}
export function updateHashManagerCapacity() {
if (!(Player.hashManager instanceof HashManager)) {
console.error(`Player does not have a HashManager`);
return;
}
const nodes = Player.hacknetNodes;
if (nodes.length === 0) {
Player.hashManager.updateCapacity(0);
return;
}
let total = 0;
for (let i = 0; i < nodes.length; ++i) {
if (typeof nodes[i] !== "string") {
Player.hashManager.updateCapacity(0);
return;
}
const h = AllServers[nodes[i]];
if (!(h instanceof HacknetServer)) {
Player.hashManager.updateCapacity(0);
return;
}
total += h.hashCapacity;
}
Player.hashManager.updateCapacity(total);
}
export function purchaseHashUpgrade(upgName, upgTarget) {
if (!(Player.hashManager instanceof HashManager)) {
console.error(`Player does not have a HashManager`);

@ -62,12 +62,14 @@ export class HacknetNode implements IHacknetNode {
// Total money earned by this Node
totalMoneyGenerated: number = 0;
constructor(name: string="") {
constructor(name: string="", prodMult: number=1) {
this.name = name;
this.updateMoneyGainRate(prodMult);
}
// Get the cost to upgrade this Node's number of cores
calculateCoreUpgradeCost(levels: number=1, p: IPlayer): number {
calculateCoreUpgradeCost(levels: number=1, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -86,13 +88,13 @@ export class HacknetNode implements IHacknetNode {
++currentCores;
}
totalCost *= p.hacknet_node_core_cost_mult;
totalCost *= costMult;
return totalCost;
}
// Get the cost to upgrade this Node's level
calculateLevelUpgradeCost(levels: number=1, p: IPlayer): number {
calculateLevelUpgradeCost(levels: number=1, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -110,11 +112,11 @@ export class HacknetNode implements IHacknetNode {
++currLevel;
}
return BaseCostForHacknetNode / 2 * totalMultiplier * p.hacknet_node_level_cost_mult;
return BaseCostForHacknetNode / 2 * totalMultiplier * costMult;
}
// Get the cost to upgrade this Node's RAM
calculateRamUpgradeCost(levels: number=1, p: IPlayer): number {
calculateRamUpgradeCost(levels: number=1, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -138,7 +140,7 @@ export class HacknetNode implements IHacknetNode {
++numUpgrades;
}
totalCost *= p.hacknet_node_ram_cost_mult;
totalCost *= costMult;
return totalCost;
}
@ -161,112 +163,37 @@ export class HacknetNode implements IHacknetNode {
// Upgrade this Node's number of cores, if possible
// Returns a boolean indicating whether new cores were successfully bought
purchaseCoreUpgrade(levels: number=1, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
if (isNaN(cost) || sanitizedLevels < 0) {
return false;
}
// Fail if we're already at max
if (this.cores >= HacknetNodeMaxCores) {
return false;
}
// If the specified number of upgrades would exceed the max Cores, calculate
// the max possible number of upgrades and use that
if (this.cores + sanitizedLevels > HacknetNodeMaxCores) {
const diff = Math.max(0, HacknetNodeMaxCores - this.cores);
return this.purchaseCoreUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
this.cores = Math.round(this.cores + sanitizedLevels); // Just in case of floating point imprecision
this.updateMoneyGainRate(p);
return true;
upgradeCore(levels: number=1, prodMult: number): void {
this.cores = Math.min(HacknetNodeMaxCores, Math.round(this.cores + levels));
this.updateMoneyGainRate(prodMult);
}
// Upgrade this Node's level, if possible
// Returns a boolean indicating whether the level was successfully updated
purchaseLevelUpgrade(levels: number=1, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
if (isNaN(cost) || sanitizedLevels < 0) {
return false;
}
// If we're at max level, return false
if (this.level >= HacknetNodeMaxLevel) {
return false;
}
// If the number of specified upgrades would exceed the max level, calculate
// the maximum number of upgrades and use that
if (this.level + sanitizedLevels > HacknetNodeMaxLevel) {
var diff = Math.max(0, HacknetNodeMaxLevel - this.level);
return this.purchaseLevelUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
this.level = Math.round(this.level + sanitizedLevels); // Just in case of floating point imprecision
this.updateMoneyGainRate(p);
return true;
upgradeLevel(levels: number=1, prodMult: number): void {
this.level = Math.min(HacknetNodeMaxLevel, Math.round(this.level + levels));
this.updateMoneyGainRate(prodMult);
}
// Upgrade this Node's RAM, if possible
// Returns a boolean indicating whether the RAM was successfully upgraded
purchaseRamUpgrade(levels: number=1, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateRamUpgradeCost(sanitizedLevels, p);
if (isNaN(cost) || sanitizedLevels < 0) {
return false;
}
// Fail if we're already at max
if (this.ram >= HacknetNodeMaxRam) {
return false;
}
// If the number of specified upgrades would exceed the max RAM, calculate the
// max possible number of upgrades and use that
if (this.ram * Math.pow(2, sanitizedLevels) > HacknetNodeMaxRam) {
var diff = Math.max(0, Math.log2(Math.round(HacknetNodeMaxRam / this.ram)));
return this.purchaseRamUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
for (let i = 0; i < sanitizedLevels; ++i) {
upgradeRam(levels: number=1, prodMult: number): void {
for (let i = 0; i < levels; ++i) {
this.ram *= 2; // Ram is always doubled
}
this.ram = Math.round(this.ram); // Handle any floating point precision issues
this.updateMoneyGainRate(p);
return true;
this.updateMoneyGainRate(prodMult);
}
// Re-calculate this Node's production and update the moneyGainRatePerSecond prop
updateMoneyGainRate(p: IPlayer): void {
updateMoneyGainRate(prodMult: number): void {
//How much extra $/s is gained per level
var gainPerLevel = HacknetNodeMoneyGainPerLevel;
this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
Math.pow(1.035, this.ram - 1) *
((this.cores + 5) / 6) *
p.hacknet_node_money_mult *
prodMult *
BitNodeMultipliers.HacknetNodeMoney;
if (isNaN(this.moneyGainRatePerSecond)) {
this.moneyGainRatePerSecond = 0;

@ -6,15 +6,16 @@ import { CONSTANTS } from "../Constants";
import { IHacknetNode } from "./IHacknetNode";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { IPlayer } from "../PersonObjects/IPlayer";
import { BaseServer } from "../Server/BaseServer";
import { RunningScript } from "../Script/RunningScript";
import { createRandomIp } from "../../utils/IPAddress";
import { Generic_fromJSON,
import {
Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
Reviver
} from "../../utils/JSONReviver";
// Constants for Hacknet Server stats/production
export const HacknetServerHashesPerLevel: number = 0.001;
@ -46,7 +47,6 @@ interface IConstructorParams {
isConnectedTo?: boolean;
maxRam?: number;
organizationName?: string;
player?: IPlayer;
}
export class HacknetServer extends BaseServer implements IHacknetNode {
@ -81,10 +81,6 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
this.maxRam = 1;
this.updateHashCapacity();
if (params.player) {
this.updateHashRate(params.player);
}
}
calculateCacheUpgradeCost(levels: number): number {
@ -109,7 +105,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
return totalCost;
}
calculateCoreUpgradeCost(levels: number, p: IPlayer): number {
calculateCoreUpgradeCost(levels: number, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -127,12 +123,12 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
++currentCores;
}
totalCost *= BaseCostForHacknetServerCore;
totalCost *= p.hacknet_node_core_cost_mult;
totalCost *= costMult;
return totalCost;
}
calculateLevelUpgradeCost(levels: number, p: IPlayer): number {
calculateLevelUpgradeCost(levels: number, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -150,10 +146,10 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
++currLevel;
}
return 10 * BaseCostForHacknetServer * totalMultiplier * p.hacknet_node_level_cost_mult;
return 10 * BaseCostForHacknetServer * totalMultiplier * costMult;
}
calculateRamUpgradeCost(levels: number, p: IPlayer): number {
calculateRamUpgradeCost(levels: number, costMult: number): number {
const sanitizedLevels = Math.round(levels);
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
return 0;
@ -175,7 +171,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
currentRam *= 2;
++numUpgrades;
}
totalCost *= p.hacknet_node_ram_cost_mult;
totalCost *= costMult;
return totalCost;
}
@ -189,124 +185,30 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
}
// Returns a boolean indicating whether the cache was successfully upgraded
purchaseCacheUpgrade(levels: number, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateCacheUpgradeCost(levels);
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
return false;
}
if (this.cache >= HacknetServerMaxCache) {
return false;
}
// If the specified number of upgrades would exceed the max, try to purchase
// the maximum possible
if (this.cache + levels > HacknetServerMaxCache) {
const diff = Math.max(0, HacknetServerMaxCache - this.cache);
return this.purchaseCacheUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
this.cache = Math.round(this.cache + sanitizedLevels);
upgradeCache(levels: number): void {
this.cache = Math.min(HacknetServerMaxCache, Math.round(this.cache + levels));
this.updateHashCapacity();
return true;
}
// Returns a boolean indicating whether the number of cores was successfully upgraded
purchaseCoreUpgrade(levels: number, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
return false;
}
if (this.cores >= HacknetServerMaxCores) {
return false;
}
// If the specified number of upgrades would exceed the max, try to purchase
// the maximum possible
if (this.cores + sanitizedLevels > HacknetServerMaxCores) {
const diff = Math.max(0, HacknetServerMaxCores - this.cores);
return this.purchaseCoreUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
this.cores = Math.round(this.cores + sanitizedLevels);
this.updateHashRate(p);
return true;
upgradeCore(levels: number, prodMult: number): void {
this.cores = Math.min(HacknetServerMaxCores, Math.round(this.cores + levels));
this.updateHashRate(prodMult);
}
// Returns a boolean indicating whether the level was successfully upgraded
purchaseLevelUpgrade(levels: number, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
return false;
}
if (this.level >= HacknetServerMaxLevel) {
return false;
}
// If the specified number of upgrades would exceed the max, try to purchase the
// maximum possible
if (this.level + sanitizedLevels > HacknetServerMaxLevel) {
const diff = Math.max(0, HacknetServerMaxLevel - this.level);
return this.purchaseLevelUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
this.level = Math.round(this.level + sanitizedLevels);
this.updateHashRate(p);
return true;
upgradeLevel(levels: number, prodMult: number): void {
this.level = Math.min(HacknetServerMaxLevel, Math.round(this.level + levels));
this.updateHashRate(prodMult);
}
// Returns a boolean indicating whether the RAM was successfully upgraded
purchaseRamUpgrade(levels: number, p: IPlayer): boolean {
const sanitizedLevels = Math.round(levels);
const cost = this.calculateRamUpgradeCost(sanitizedLevels, p);
if(isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
return false;
}
if (this.maxRam >= HacknetServerMaxRam) {
return false;
}
// If the specified number of upgrades would exceed the max, try to purchase
// just the maximum possible
if (this.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerMaxRam) {
const diff = Math.max(0, Math.log2(Math.round(HacknetServerMaxRam / this.maxRam)));
return this.purchaseRamUpgrade(diff, p);
}
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
for (let i = 0; i < sanitizedLevels; ++i) {
upgradeRam(levels: number, prodMult: number): boolean {
for (let i = 0; i < levels; ++i) {
this.maxRam *= 2;
}
this.maxRam = Math.round(this.maxRam);
this.updateHashRate(p);
this.maxRam = Math.min(HacknetServerMaxRam, Math.round(this.maxRam));
this.updateHashRate(prodMult);
return true;
}
@ -314,10 +216,10 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
/**
* Whenever a script is run, we must update this server's hash rate
*/
runScript(script: RunningScript, p?: IPlayer): void {
runScript(script: RunningScript, prodMult?: number): void {
super.runScript(script);
if (p) {
this.updateHashRate(p);
if (prodMult != null && typeof prodMult === "number") {
this.updateHashRate(prodMult);
}
}
@ -325,7 +227,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
this.hashCapacity = 32 * Math.pow(2, this.cache);
}
updateHashRate(p: IPlayer): void {
updateHashRate(prodMult: number): void {
const baseGain = HacknetServerHashesPerLevel * this.level;
const ramMultiplier = Math.pow(1.07, Math.log2(this.maxRam));
const coreMultiplier = 1 + (this.cores - 1) / 5;
@ -333,7 +235,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
const hashRate = baseGain * ramMultiplier * coreMultiplier * ramRatio;
this.hashRate = hashRate * p.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney;
this.hashRate = hashRate * prodMult * BitNodeMultipliers.HacknetNodeMoney;
if (isNaN(this.hashRate)) {
this.hashRate = 0;

@ -6,12 +6,9 @@
* his hashes, and contains method for grabbing the data/multipliers from
* those upgrades
*/
import { HacknetServer } from "./HacknetServer";
import { HashUpgrades } from "./HashUpgrades";
import { IMap } from "../types";
import { IPlayer } from "../PersonObjects/IPlayer";
import { AllServers } from "../Server/AllServers";
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
@ -84,14 +81,14 @@ export class HashManager {
return upg.getCost(currLevel);
}
prestige(p: IPlayer): void {
prestige(): void {
for (const name in HashUpgrades) {
this.upgrades[name] = 0;
}
this.hashes = 0;
if (p != null) {
this.updateCapacity(p);
}
// When prestiging, player's hacknet nodes are always reset. So capacity = 0
this.updateCapacity(0);
}
/**
@ -118,33 +115,11 @@ export class HashManager {
this.hashes = Math.min(this.hashes, this.capacity);
}
updateCapacity(p: IPlayer): void {
if (p.hacknetNodes.length <= 0) {
updateCapacity(newCap: number): void {
if (newCap < 0) {
this.capacity = 0;
return;
}
// Make sure the Player's `hacknetNodes` property actually holds Hacknet Servers
const ip: string = <string>p.hacknetNodes[0];
if (typeof ip !== "string") {
this.capacity = 0;
return;
}
const hserver = <HacknetServer>AllServers[ip];
if (!(hserver instanceof HacknetServer)) {
this.capacity = 0;
return;
}
let total: number = 0;
for (let i = 0; i < p.hacknetNodes.length; ++i) {
const h = <HacknetServer>AllServers[<string>p.hacknetNodes[i]];
total += h.hashCapacity;
}
this.capacity = total;
this.capacity = Math.max(newCap, 0);
}
/**

@ -1,16 +1,14 @@
// Interface for a Hacknet Node. Implemented by both a basic Hacknet Node,
// and the upgraded Hacknet Server in BitNode-9
import { IPlayer } from "../PersonObjects/IPlayer";
export interface IHacknetNode {
cores: number;
level: number;
onlineTimeSeconds: number;
calculateCoreUpgradeCost: (levels: number, p: IPlayer) => number;
calculateLevelUpgradeCost: (levels: number, p: IPlayer) => number;
calculateRamUpgradeCost: (levels: number, p: IPlayer) => number;
purchaseCoreUpgrade: (levels: number, p: IPlayer) => boolean;
purchaseLevelUpgrade: (levels: number, p: IPlayer) => boolean;
purchaseRamUpgrade: (levels: number, p: IPlayer) => boolean;
calculateCoreUpgradeCost: (levels: number, costMult: number) => number;
calculateLevelUpgradeCost: (levels: number, costMult: number) => number;
calculateRamUpgradeCost: (levels: number, costMult: number) => number;
upgradeCore: (levels: number, prodMult: number) => void;
upgradeLevel: (levels: number, prodMult: number) => void;
upgradeRam: (levels: number, prodMult: number) => void;
}

@ -4,12 +4,19 @@
*/
import React from "react";
import { HacknetNodeMaxLevel,
import {
HacknetNodeMaxLevel,
HacknetNodeMaxRam,
HacknetNodeMaxCores } from "../HacknetNode";
import { getMaxNumberLevelUpgrades,
HacknetNodeMaxCores
} from "../HacknetNode";
import {
getMaxNumberLevelUpgrades,
getMaxNumberRamUpgrades,
getMaxNumberCoreUpgrades } from "../HacknetHelpers";
getMaxNumberCoreUpgrades,
purchaseLevelUpgrade,
purchaseRamUpgrade,
purchaseCoreUpgrade,
} from "../HacknetHelpers";
import { Player } from "../../Player";
@ -35,7 +42,7 @@ export class HacknetNode extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player.hacknet_node_level_cost_mult);
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
if (Player.money.lt(upgradeLevelCost)) {
upgradeLevelClass = "std-button-disabled";
@ -48,7 +55,7 @@ export class HacknetNode extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetNodeMaxLevel);
}
node.purchaseLevelUpgrade(numUpgrades, Player);
purchaseLevelUpgrade(node, numUpgrades);
recalculate();
return false;
}
@ -66,7 +73,7 @@ export class HacknetNode extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player.hacknet_node_ram_cost_mult);
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
if (Player.money.lt(upgradeRamCost)) {
upgradeRamClass = "std-button-disabled";
@ -79,7 +86,7 @@ export class HacknetNode extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberRamUpgrades(node, HacknetNodeMaxRam);
}
node.purchaseRamUpgrade(numUpgrades, Player);
purchaseRamUpgrade(node, numUpgrades);
recalculate();
return false;
}
@ -97,7 +104,7 @@ export class HacknetNode extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player.hacknet_node_core_cost_mult);
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
if (Player.money.lt(upgradeCoreCost)) {
upgradeCoresClass = "std-button-disabled";
@ -110,7 +117,7 @@ export class HacknetNode extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetNodeMaxCores);
}
node.purchaseCoreUpgrade(numUpgrades, Player);
purchaseCoreUpgrade(node, numUpgrades);
recalculate();
return false;
}

@ -4,14 +4,23 @@
*/
import React from "react";
import { HacknetServerMaxLevel,
import {
HacknetServerMaxLevel,
HacknetServerMaxRam,
HacknetServerMaxCores,
HacknetServerMaxCache } from "../HacknetServer";
import { getMaxNumberLevelUpgrades,
HacknetServerMaxCache
} from "../HacknetServer";
import {
getMaxNumberLevelUpgrades,
getMaxNumberRamUpgrades,
getMaxNumberCoreUpgrades,
getMaxNumberCacheUpgrades } from "../HacknetHelpers";
getMaxNumberCacheUpgrades,
purchaseLevelUpgrade,
purchaseRamUpgrade,
purchaseCoreUpgrade,
purchaseCacheUpgrade,
updateHashManagerCapacity,
} from "../HacknetHelpers";
import { Player } from "../../Player";
@ -37,7 +46,7 @@ export class HacknetServer extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player.hacknet_node_level_cost_mult);
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
if (Player.money.lt(upgradeLevelCost)) {
upgradeLevelClass = "std-button-disabled";
@ -50,7 +59,7 @@ export class HacknetServer extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetServerMaxLevel);
}
node.purchaseLevelUpgrade(numUpgrades, Player);
purchaseLevelUpgrade(node, numUpgrades);
recalculate();
return false;
}
@ -69,7 +78,7 @@ export class HacknetServer extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player.hacknet_node_ram_cost_mult);
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
if (Player.money.lt(upgradeRamCost)) {
upgradeRamClass = "std-button-disabled";
@ -82,7 +91,7 @@ export class HacknetServer extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberRamUpgrades(node, HacknetServerMaxRam);
}
node.purchaseRamUpgrade(numUpgrades, Player);
purchaseRamUpgrade(node, numUpgrades);
recalculate();
return false;
}
@ -101,7 +110,7 @@ export class HacknetServer extends React.Component {
multiplier = Math.min(levelsToMax, purchaseMult);
}
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player.hacknet_node_core_cost_mult);
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
if (Player.money.lt(upgradeCoreCost)) {
upgradeCoresClass = "std-button-disabled";
@ -114,7 +123,7 @@ export class HacknetServer extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetServerMaxCores);
}
node.purchaseCoreUpgrade(numUpgrades, Player);
purchaseCoreUpgrade(node, numUpgrades);
recalculate();
return false;
}
@ -146,9 +155,9 @@ export class HacknetServer extends React.Component {
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberCacheUpgrades(node, HacknetServerMaxCache);
}
node.purchaseCacheUpgrade(numUpgrades, Player);
purchaseCacheUpgrade(node, numUpgrades);
recalculate();
Player.hashManager.updateCapacity(Player);
updateHashManagerCapacity();
return false;
}

@ -7,27 +7,32 @@ import { CONSTANTS } from "../Constants";
import { CityName } from "./data/CityNames";
import { IPlayer } from "../PersonObjects/IPlayer";
import { AllServers,
AddToAllServers } from "../Server/AllServers";
import { Server } from "../Server/Server";
import { getPurchaseServerCost,
import {
AddToAllServers,
createUniqueRandomIp,
} from "../Server/AllServers";
import { safetlyCreateUniqueServer } from "../Server/ServerHelpers";
import {
getPurchaseServerCost,
purchaseRamForHomeComputer,
purchaseServer } from "../Server/ServerPurchases";
purchaseServer
} from "../Server/ServerPurchases";
import { SpecialServerIps } from "../Server/SpecialServerIps";
import { Settings } from "../Settings/Settings";
import { numeralWrapper } from "../ui/numeralFormat";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { createRandomIp } from "../../utils/IPAddress";
import { yesNoBoxGetYesButton,
import {
yesNoBoxGetYesButton,
yesNoBoxGetNoButton,
yesNoBoxClose,
yesNoBoxCreate,
yesNoTxtInpBoxGetYesButton,
yesNoTxtInpBoxGetNoButton,
yesNoTxtInpBoxClose,
yesNoTxtInpBoxCreate } from "../../utils/YesNoBox";
yesNoTxtInpBoxCreate
} from "../../utils/YesNoBox";
import { createElement } from "../../utils/uiHelpers/createElement";
import { createPopup } from "../../utils/uiHelpers/createPopup";
@ -271,8 +276,8 @@ export function purchaseTorRouter(p: IPlayer) {
}
p.loseMoney(CONSTANTS.TorRouterCost);
const darkweb = new Server({
ip: createRandomIp(), hostname:"darkweb", organizationName:"",
const darkweb = safetlyCreateUniqueServer({
ip: createUniqueRandomIp(), hostname:"darkweb", organizationName:"",
isConnectedTo:false, adminRights:false, purchasedByPlayer:false, maxRam:1
});
AddToAllServers(darkweb);

@ -0,0 +1,72 @@
/**
* The environment in which a script runs. The environment holds
* Netscript functions and arguments for that script.
*/
import { IMap } from "../types";
export class Environment {
/**
* Parent environment. Used to implement "scope"
*/
parent: Environment | null = null;
/**
* Whether or not the script that uses this Environment should stop running
*/
stopFlag: boolean = false;
/**
* Environment variables (currently only Netscript functions)
*/
vars: IMap<any> = {};
constructor(parent: Environment | null) {
if (parent instanceof Environment) {
this.vars = Object.assign({}, parent.vars);
}
this.parent = parent;
}
/**
* Finds the scope where the variable with the given name is defined
*/
lookup(name: string): Environment | null {
let scope: Environment | null = this;
while (scope) {
if (Object.prototype.hasOwnProperty.call(scope.vars, name)) {
return scope;
}
scope = scope.parent;
}
return null;
}
//Get the current value of a variable
get(name: string): any {
if (name in this.vars) {
return this.vars[name];
}
throw new Error(`Undefined variable ${name}`);
}
//Sets the value of a variable in any scope
set(name: string, value: any) {
var scope = this.lookup(name);
//If scope has a value, then this variable is already set in a higher scope, so
//set is there. Otherwise, create a new variable in the local scope
if (scope !== null) {
return scope.vars[name] = value;
} else {
return this.vars[name] = value;
}
}
//Creates (or overwrites) a variable in the current scope
def(name: string, value: any) {
return this.vars[name] = value;
}
}

@ -0,0 +1,332 @@
import { IMap } from "../types";
// TODO remember to update RamCalculations.js and WorkerScript.js
// RAM costs for Netscript functions
export const RamCostConstants: IMap<number> = {
ScriptBaseRamCost: 1.6,
ScriptDomRamCost: 25,
ScriptHackRamCost: 0.1,
ScriptHackAnalyzeRamCost: 1,
ScriptGrowRamCost: 0.15,
ScriptGrowthAnalyzeRamCost: 1,
ScriptWeakenRamCost: 0.15,
ScriptScanRamCost: 0.2,
ScriptPortProgramRamCost: 0.05,
ScriptRunRamCost: 1.0,
ScriptExecRamCost: 1.3,
ScriptSpawnRamCost: 2.0,
ScriptScpRamCost: 0.6,
ScriptKillRamCost: 0.5,
ScriptHasRootAccessRamCost: 0.05,
ScriptGetHostnameRamCost: 0.05,
ScriptGetHackingLevelRamCost: 0.05,
ScriptGetMultipliersRamCost: 4.0,
ScriptGetServerRamCost: 0.1,
ScriptFileExistsRamCost: 0.1,
ScriptIsRunningRamCost: 0.1,
ScriptHacknetNodesRamCost: 4.0,
ScriptHNUpgLevelRamCost: 0.4,
ScriptHNUpgRamRamCost: 0.6,
ScriptHNUpgCoreRamCost: 0.8,
ScriptGetStockRamCost: 2.0,
ScriptBuySellStockRamCost: 2.5,
ScriptGetPurchaseServerRamCost: 0.25,
ScriptPurchaseServerRamCost: 2.25,
ScriptGetPurchasedServerLimit: 0.05,
ScriptGetPurchasedServerMaxRam: 0.05,
ScriptRoundRamCost: 0.05,
ScriptReadWriteRamCost: 1.0,
ScriptArbScriptRamCost: 1.0,
ScriptGetScriptRamCost: 0.1,
ScriptGetHackTimeRamCost: 0.05,
ScriptGetFavorToDonate: 0.10,
ScriptCodingContractBaseRamCost: 10,
ScriptSleeveBaseRamCost: 4,
ScriptSingularityFn1RamCost: 2,
ScriptSingularityFn2RamCost: 3,
ScriptSingularityFn3RamCost: 5,
ScriptGangApiBaseRamCost: 4,
ScriptBladeburnerApiBaseRamCost: 4,
}
export const RamCosts: IMap<any> = {
hacknet: {
numNodes: () => 0,
purchaseNode: () => 0,
getPurchaseNodeCost: () => 0,
getNodeStats: () => 0,
upgradeLevel: () => 0,
upgradeRam: () => 0,
upgradeCore: () => 0,
upgradeCache: () => 0,
getLevelUpgradeCost: () => 0,
getRamUpgradeCost: () => 0,
getCoreUpgradeCost: () => 0,
getCacheUpgradeCost: () => 0,
numHashes: () => 0,
hashCost: () => 0,
spendHashes: () => 0,
},
sprintf: () => 0,
vsprintf: () => 0,
scan: () => RamCostConstants.ScriptScanRamCost,
hack: () => RamCostConstants.ScriptHackRamCost,
hackAnalyzeThreads: () => RamCostConstants.ScriptHackAnalyzeRamCost,
hackAnalyzePercent: () => RamCostConstants.ScriptHackAnalyzeRamCost,
hackChance: () => RamCostConstants.ScriptHackAnalyzeRamCost,
sleep: () => 0,
grow: () => RamCostConstants.ScriptGrowRamCost,
growthAnalyze: () => RamCostConstants.ScriptGrowthAnalyzeRamCost,
weaken: () => RamCostConstants.ScriptWeakenRamCost,
print: () => 0,
tprint: () => 0,
clearLog: () => 0,
disableLog: () => 0,
enableLog: () => 0,
isLogEnabled: () => 0,
getScriptLogs: () => 0,
nuke: () => RamCostConstants.ScriptPortProgramRamCost,
brutessh: () => RamCostConstants.ScriptPortProgramRamCost,
ftpcrack: () => RamCostConstants.ScriptPortProgramRamCost,
relaysmtp: () => RamCostConstants.ScriptPortProgramRamCost,
httpworm: () => RamCostConstants.ScriptPortProgramRamCost,
sqlinject: () => RamCostConstants.ScriptPortProgramRamCost,
run: () => RamCostConstants.ScriptRunRamCost,
exec: () => RamCostConstants.ScriptExecRamCost,
spawn: () => RamCostConstants.ScriptSpawnRamCost,
kill: () => RamCostConstants.ScriptKillRamCost,
killall: () => RamCostConstants.ScriptKillRamCost,
exit: () => 0,
scp: () => RamCostConstants.ScriptScpRamCost,
ls: () => RamCostConstants.ScriptScanRamCost,
ps: () => RamCostConstants.ScriptScanRamCost,
hasRootAccess: () => RamCostConstants.ScriptHasRootAccessRamCost,
getIp: () => RamCostConstants.ScriptGetHostnameRamCost,
getHostname: () => RamCostConstants.ScriptGetHostnameRamCost,
getHackingLevel: () => RamCostConstants.ScriptGetHackingLevelRamCost,
getHackingMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost,
getHacknetMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost,
getBitNodeMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost,
getServerMoneyAvailable: () => RamCostConstants.ScriptGetServerRamCost,
getServerSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost,
getServerBaseSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost,
getServerMinSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost,
getServerRequiredHackingLevel: () => RamCostConstants.ScriptGetServerRamCost,
getServerMaxMoney: () => RamCostConstants.ScriptGetServerRamCost,
getServerGrowth: () => RamCostConstants.ScriptGetServerRamCost,
getServerNumPortsRequired: () => RamCostConstants.ScriptGetServerRamCost,
getServerRam: () => RamCostConstants.ScriptGetServerRamCost,
serverExists: () => RamCostConstants.ScriptGetServerRamCost,
fileExists: () => RamCostConstants.ScriptFileExistsRamCost,
isRunning: () => RamCostConstants.ScriptIsRunningRamCost,
getStockSymbols: () => RamCostConstants.ScriptGetStockRamCost,
getStockPrice: () => RamCostConstants.ScriptGetStockRamCost,
getStockAskPrice: () => RamCostConstants.ScriptGetStockRamCost,
getStockBidPrice: () => RamCostConstants.ScriptGetStockRamCost,
getStockPosition: () => RamCostConstants.ScriptGetStockRamCost,
getStockMaxShares: () => RamCostConstants.ScriptGetStockRamCost,
getStockPurchaseCost: () => RamCostConstants.ScriptGetStockRamCost,
getStockSaleGain: () => RamCostConstants.ScriptGetStockRamCost,
buyStock: () => RamCostConstants.ScriptBuySellStockRamCost,
sellStock: () => RamCostConstants.ScriptBuySellStockRamCost,
shortStock: () => RamCostConstants.ScriptBuySellStockRamCost,
sellShort: () => RamCostConstants.ScriptBuySellStockRamCost,
placeOrder: () => RamCostConstants.ScriptBuySellStockRamCost,
cancelOrder: () => RamCostConstants.ScriptBuySellStockRamCost,
getOrders: () => RamCostConstants.ScriptBuySellStockRamCost,
getStockVolatility: () => RamCostConstants.ScriptBuySellStockRamCost,
getStockForecast: () => RamCostConstants.ScriptBuySellStockRamCost,
purchase4SMarketData: () => RamCostConstants.ScriptBuySellStockRamCost,
purchase4SMarketDataTixApi: () => RamCostConstants.ScriptBuySellStockRamCost,
getPurchasedServerLimit: () => RamCostConstants.ScriptGetPurchasedServerLimit,
getPurchasedServerMaxRam: () => RamCostConstants.ScriptGetPurchasedServerMaxRam,
getPurchasedServerCost: () => RamCostConstants.ScriptGetPurchaseServerRamCost,
purchaseServer: () => RamCostConstants.ScriptPurchaseServerRamCost,
deleteServer: () => RamCostConstants.ScriptPurchaseServerRamCost,
getPurchasedServers: () => RamCostConstants.ScriptPurchaseServerRamCost,
write: () => RamCostConstants.ScriptReadWriteRamCost,
tryWrite: () => RamCostConstants.ScriptReadWriteRamCost,
read: () => RamCostConstants.ScriptReadWriteRamCost,
peek: () => RamCostConstants.ScriptReadWriteRamCost,
clear: () => RamCostConstants.ScriptReadWriteRamCost,
getPortHandle: () => RamCostConstants.ScriptReadWriteRamCost * 10,
rm: () => RamCostConstants.ScriptReadWriteRamCost,
scriptRunning: () => RamCostConstants.ScriptArbScriptRamCost,
scriptKill: () => RamCostConstants.ScriptArbScriptRamCost,
getScriptName: () => 0,
getScriptRam: () => RamCostConstants.ScriptGetScriptRamCost,
getHackTime: () => RamCostConstants.ScriptGetHackTimeRamCost,
getGrowTime: () => RamCostConstants.ScriptGetHackTimeRamCost,
getWeakenTime: () => RamCostConstants.ScriptGetHackTimeRamCost,
getScriptIncome: () => RamCostConstants.ScriptGetScriptRamCost,
getScriptExpGain: () => RamCostConstants.ScriptGetScriptRamCost,
nFormat: () => 0,
getTimeSinceLastAug: () => RamCostConstants.ScriptGetHackTimeRamCost,
prompt: () => 0,
wget: () => 0,
getFavorToDonate: () => RamCostConstants.ScriptGetFavorToDonate,
// Singularity Functions
universityCourse: () => RamCostConstants.ScriptSingularityFn1RamCost,
gymWorkout: () => RamCostConstants.ScriptSingularityFn1RamCost,
travelToCity: () => RamCostConstants.ScriptSingularityFn1RamCost,
purchaseTor: () => RamCostConstants.ScriptSingularityFn1RamCost,
purchaseProgram: () => RamCostConstants.ScriptSingularityFn1RamCost,
getStats: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
getCharacterInformation: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
isBusy: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
stopAction: () => RamCostConstants.ScriptSingularityFn1RamCost / 2,
upgradeHomeRam: () => RamCostConstants.ScriptSingularityFn2RamCost,
getUpgradeHomeRamCost: () => RamCostConstants.ScriptSingularityFn2RamCost / 2,
workForCompany: () => RamCostConstants.ScriptSingularityFn2RamCost,
applyToCompany: () => RamCostConstants.ScriptSingularityFn2RamCost,
getCompanyRep: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
getCompanyFavor: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
getCompanyFavorGain: () => RamCostConstants.ScriptSingularityFn2RamCost / 4,
checkFactionInvitations: () => RamCostConstants.ScriptSingularityFn2RamCost,
joinFaction: () => RamCostConstants.ScriptSingularityFn2RamCost,
workForFaction: () => RamCostConstants.ScriptSingularityFn2RamCost,
getFactionRep: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
getFactionFavor: () => RamCostConstants.ScriptSingularityFn2RamCost / 3,
getFactionFavorGain: () => RamCostConstants.ScriptSingularityFn2RamCost / 4,
donateToFaction: () => RamCostConstants.ScriptSingularityFn3RamCost,
createProgram: () => RamCostConstants.ScriptSingularityFn3RamCost,
commitCrime: () => RamCostConstants.ScriptSingularityFn3RamCost,
getCrimeChance: () => RamCostConstants.ScriptSingularityFn3RamCost,
getOwnedAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost,
getOwnedSourceFiles: () => RamCostConstants.ScriptSingularityFn3RamCost,
getAugmentationsFromFaction: () => RamCostConstants.ScriptSingularityFn3RamCost,
getAugmentationPrereq: () => RamCostConstants.ScriptSingularityFn3RamCost,
getAugmentationCost: () => RamCostConstants.ScriptSingularityFn3RamCost,
purchaseAugmentation: () => RamCostConstants.ScriptSingularityFn3RamCost,
installAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost,
// Gang API
gang : {
getMemberNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
getGangInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getOtherGangInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getMemberInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
canRecruitMember: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
recruitMember: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getTaskNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
setMemberTask: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getEquipmentNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
getEquipmentCost: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getEquipmentType: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
purchaseEquipment: () => RamCostConstants.ScriptGangApiBaseRamCost,
ascendMember: () => RamCostConstants.ScriptGangApiBaseRamCost,
setTerritoryWarfare: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getChanceToWinClash: () => RamCostConstants.ScriptGangApiBaseRamCost,
getBonusTime: () => 0,
},
// Bladeburner API
bladeburner : {
getContractNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getOperationNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getBlackOpNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getBlackOpRank: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2,
getGeneralActionNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getSkillNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
startAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
stopBladeburnerAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2,
getCurrentAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 4,
getActionTime: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionEstimatedSuccessChance: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionRepGain: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionCountRemaining: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionMaxLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionCurrentLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionAutolevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setActionAutolevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setActionLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getRank: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillPoints: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillUpgradeCost: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
upgradeSkill: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getTeamSize: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setTeamSize: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityEstimatedPopulation: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityEstimatedCommunities: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityChaos: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCity: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
switchCity: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getStamina: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
joinBladeburnerFaction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
joinBladeburnerDivision: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getBonusTime: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost,
},
// Coding Contract API
codingcontract : {
attempt: () => RamCostConstants.ScriptCodingContractBaseRamCost,
getContractType: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getData: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getDescription: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getNumTriesRemaining: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
},
// Duplicate Sleeve API
sleeve : {
getNumSleeves: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToShockRecovery: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToSynchronize: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToCommitCrime: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToUniversityCourse: () => RamCostConstants.ScriptSleeveBaseRamCost,
travel: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToCompanyWork: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToFactionWork: () => RamCostConstants.ScriptSleeveBaseRamCost,
setToGymWorkout: () => RamCostConstants.ScriptSleeveBaseRamCost,
getSleeveStats: () => RamCostConstants.ScriptSleeveBaseRamCost,
getTask: () => RamCostConstants.ScriptSleeveBaseRamCost,
getInformation: () => RamCostConstants.ScriptSleeveBaseRamCost,
getSleeveAugmentations: () => RamCostConstants.ScriptSleeveBaseRamCost,
getSleevePurchasableAugs: () => RamCostConstants.ScriptSleeveBaseRamCost,
purchaseSleeveAug: () => RamCostConstants.ScriptSleeveBaseRamCost,
},
heart: {
// Easter egg function
break : () => 0,
}
}
export function getRamCost(...args: string[]): number {
if (args.length === 0) {
console.warn(`No arguments passed to getRamCost()`);
return 0;
}
let curr = RamCosts[args[0]];
for (let i = 1; i < args.length; ++i) {
if (curr == null) {
console.warn(`Invalid function passed to getRamCost: ${args}`);
return 0;
}
const currType = typeof curr;
if (currType === "function" || currType === "number") {
break;
}
curr = curr[args[i]];
}
const currType = typeof curr;
if (currType === "function") {
return curr();
}
if (currType === "number") {
return curr;
}
console.warn(`Expected type: ${currType}`);
return 0;
}

@ -0,0 +1,176 @@
/**
* The worker agent for running a script instance. Each running script instance
* has its own underlying WorkerScript object.
*
* Note that these objects are not saved and re-loaded when the game is refreshed.
* Instead, whenever the game is opened, WorkerScripts are re-created from
* RunningScript objects
*/
import { Environment } from "./Environment";
import { RamCostConstants } from "./RamCostGenerator";
import { RunningScript } from "../Script/RunningScript";
import { Script } from "../Script/Script";
import { AllServers } from "../Server/AllServers";
import { BaseServer } from "../Server/BaseServer";
import { IMap } from "../types";
export class WorkerScript {
/**
* Script's arguments
*/
args: any[];
/**
* Copy of the script's code
*/
code: string = "";
/**
* Holds the timeoutID (numeric value) for whenever this script is blocked by a
* timed Netscript function. i.e. Holds the return value of setTimeout()
*/
delay: number | null = null;
/**
* Stores names of all functions that have logging disabled
*/
disableLogs: IMap<string> = {};
/**
* Used for dynamic RAM calculation. Stores names of all functions that have
* already been checked by this script.
* TODO: Could probably just combine this with loadedFns?
*/
dynamicLoadedFns: IMap<string> = {};
/**
* Tracks dynamic RAM usage
*/
dynamicRamUsage: number = RamCostConstants.ScriptBaseRamCost;
/**
* Netscript Environment for this script
*/
env: Environment;
/**
* Status message in case of script error. Currently unused I think
*/
errorMessage: string = "";
/**
* Used for static RAM calculation. Stores names of all functions that have
* already been checked by this script
*/
loadedFns: IMap<string> = {};
/**
* Filename of script
*/
name: string;
/**
* Script's output/return value. Currently not used or implemented
*/
output: string = "";
/**
* Script's RAM usage. Equivalent to underlying script's RAM usage
*/
ramUsage: number = 0;
/**
* Whether or not this workerScript is currently running
*/
running: boolean = false;
/**
* Reference to underlying RunningScript object
*/
scriptRef: RunningScript;
/**
* IP Address on which this script is running
*/
serverIp: string;
constructor(runningScriptObj: RunningScript, nsFuncsGenerator?: (ws: WorkerScript) => object) {
this.name = runningScriptObj.filename;
this.serverIp = runningScriptObj.server;
// Get the underlying script's code
const server = AllServers[this.serverIp];
if (server == null) {
throw new Error(`WorkerScript constructed with invalid server ip: ${this.serverIp}`);
}
let found = false;
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.name) {
found = true;
this.code = server.scripts[i].code;
}
}
if (!found) {
throw new Error(`WorkerScript constructed with invalid script filename: ${this.name}`);
}
this.env = new Environment(null);
if (typeof nsFuncsGenerator === "function") {
this.env.vars = nsFuncsGenerator(this);
}
this.env.set("args", runningScriptObj.args.slice());
this.scriptRef = runningScriptObj;
this.args = runningScriptObj.args.slice();
}
/**
* Returns the Server on which this script is running
*/
getServer() {
return AllServers[this.serverIp];
}
/**
* Returns the Script object for the underlying script.
* Returns null if it cannot be found (which would be a bug)
*/
getScript(): Script | null {
let server = this.getServer();
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.name) {
return server.scripts[i];
}
}
console.error("Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong");
return null;
}
/**
* Returns the script with the specified filename on the specified server,
* or null if it cannot be found
*/
getScriptOnServer(fn: string, server: BaseServer): Script | null {
if (server == null) {
server = this.getServer();
}
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === fn) {
return server.scripts[i];
}
}
return null;
}
shouldLog(fn: string): boolean {
return (this.disableLogs.ALL == null && this.disableLogs[fn] == null);
}
log(txt: string): void {
this.scriptRef.log(txt);
}
}

@ -1,64 +0,0 @@
import { NetscriptFunctions } from "./NetscriptFunctions";
import { NetscriptPort } from "./NetscriptPort";
/* Environment
* NetScript program environment
*/
function Environment(workerScript,parent) {
if (parent){
//Create a copy of parent's variables
//this.vars = parent.vars;
this.vars = Object.assign({}, parent.vars);
} else {
this.vars = NetscriptFunctions(workerScript);
}
this.parent = parent;
this.stopFlag = false;
}
Environment.prototype = {
//Create a "subscope", which is a new new "sub-environment"
//The subscope is linked to this through its parent variable
extend: function() {
return new Environment(null, this);
},
//Finds the scope where the variable with the given name is defined
lookup: function(name) {
var scope = this;
while (scope) {
if (Object.prototype.hasOwnProperty.call(scope.vars, name)) {
return scope;
}
scope = scope.parent;
}
return null;
},
//Get the current value of a variable
get: function(name) {
if (name in this.vars) {
return this.vars[name];
}
throw new Error("Undefined variable " + name);
},
//Sets the value of a variable in any scope
set: function(name, value) {
var scope = this.lookup(name);
//If scope has a value, then this variable is already set in a higher scope, so
//set is there. Otherwise, create a new variable in the local scope
if (scope !== null) {
return scope.vars[name] = value;
} else {
return this.vars[name] = value;
}
},
//Creates (or overwrites) a variable in the current scope
def: function(name, value) {
return this.vars[name] = value;
}
};
export {Environment};

@ -1,132 +1,12 @@
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Player } from "./Player";
import { Environment } from "./NetscriptEnvironment";
import { WorkerScript, addWorkerScript } from "./NetscriptWorker";
import { Server } from "./Server/Server";
import { WorkerScript } from "./Netscript/WorkerScript";
import { getServer } from "./Server/ServerHelpers";
import { Settings } from "./Settings/Settings";
import { RunningScript } from "./Script/RunningScript";
import { Script } from "./Script/Script";
import { findRunningScript } from "./Script/ScriptHelpers";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { parse, Node } from "../utils/acorn";
import { arrayToString } from "../utils/helpers/arrayToString";
import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
import { isString } from "../utils/helpers/isString";
export function evaluateImport(exp, workerScript, checkingRam=false) {
//When its checking RAM, it exports an array of nodes for each imported function
var ramCheckRes = [];
var env = workerScript.env;
if (env.stopFlag) {
if (checkingRam) {return ramCheckRes;}
return Promise.reject(workerScript);
}
//Get source script and name of all functions to import
var scriptName = exp.source.value;
var namespace, namespaceObj, allFns = false, fnNames = [];
if (exp.specifiers.length === 1 && exp.specifiers[0].type === "ImportNamespaceSpecifier") {
allFns = true;
namespace = exp.specifiers[0].local.name;
} else {
for (var i = 0; i < exp.specifiers.length; ++i) {
fnNames.push(exp.specifiers[i].local.name);
}
}
//Get the code
var server = getServer(workerScript.serverIp), code = "";
if (server == null) {
if (checkingRam) {return ramCheckRes;}
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to identify server. This is a bug please report to dev", exp));
}
for (var i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === scriptName) {
code = server.scripts[i].code;
break;
}
}
if (code === "") {
if (checkingRam) {return ramCheckRes;}
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Could not find script " + scriptName + " to import", exp));
}
//Create the AST
try {
var ast = parse(code, {sourceType:"module"});
} catch(e) {
console.log("Failed to parse import script");
if (checkingRam) {return ramCheckRes;}
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to import functions from " + scriptName +
" This is most likely due to a syntax error in the imported script", exp));
}
if (allFns) {
//A namespace is implemented as a JS obj
env.set(namespace, {});
namespaceObj = env.get(namespace);
}
//Search through the AST for all imported functions
var queue = [ast];
while (queue.length != 0) {
var node = queue.shift();
switch (node.type) {
case "BlockStatement":
case "Program":
for (var i = 0; i < node.body.length; ++i) {
if (node.body[i] instanceof Node) {
queue.push(node.body[i]);
}
}
break;
case "FunctionDeclaration":
if (node.id && node.id.name) {
if (allFns) {
//Import all functions under this namespace
if (checkingRam) {
ramCheckRes.push(node);
} else {
namespaceObj[node.id.name] = node;
}
} else {
//Only import specified functions
if (fnNames.includes(node.id.name)) {
if (checkingRam) {
ramCheckRes.push(node);
} else {
env.set(node.id.name, node);
}
}
}
} else {
if (checkingRam) {return ramCheckRes;}
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid function declaration in imported script " + scriptName, exp));
}
break;
default:
break;
}
for (var prop in node) {
if (node.hasOwnProperty(prop)) {
if (node[prop] instanceof Node) {
queue.push(node[prop]);
}
}
}
}
if (!checkingRam) {workerScript.scriptRef.log("Imported functions from " + scriptName);}
if (checkingRam) {return ramCheckRes;}
return Promise.resolve(true);
}
export function killNetscriptDelay(workerScript) {
if (workerScript instanceof WorkerScript) {
if (workerScript.delay) {
@ -155,60 +35,6 @@ export function makeRuntimeRejectMsg(workerScript, msg, exp=null) {
return "|"+workerScript.serverIp+"|"+workerScript.name+"|" + msg + lineNum;
}
//Run a script from inside a script using run() command
export function runScriptFromScript(server, scriptname, args, workerScript, threads=1) {
//Check if the script is already running
let runningScriptObj = findRunningScript(scriptname, args, server);
if (runningScriptObj != null) {
workerScript.scriptRef.log(scriptname + " is already running on " + server.hostname);
return Promise.resolve(false);
}
//'null/undefined' arguments are not allowed
for (let i = 0; i < args.length; ++i) {
if (args[i] == null) {
workerScript.scriptRef.log("ERROR: Cannot execute a script with null/undefined as an argument");
return Promise.resolve(false);
}
}
//Check if the script exists and if it does run it
for (var i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename == scriptname) {
//Check for admin rights and that there is enough RAM availble to run
var script = server.scripts[i];
var ramUsage = script.ramUsage;
threads = Math.round(Number(threads)); //Convert to number and round
if (threads === 0) { return Promise.resolve(false); }
ramUsage = ramUsage * threads;
var ramAvailable = server.maxRam - server.ramUsed;
if (server.hasAdminRights == false) {
workerScript.scriptRef.log("Cannot run script " + scriptname + " on " + server.hostname + " because you do not have root access!");
return Promise.resolve(false);
} else if (ramUsage > ramAvailable){
workerScript.scriptRef.log("Cannot run script " + scriptname + "(t=" + threads + ") on " + server.hostname + " because there is not enough available RAM!");
return Promise.resolve(false);
} else {
//Able to run script
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...`);
}
let runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads;
addWorkerScript(runningScriptObj, server);
// Push onto runningScripts.
// This has to come after addWorkerScript() because that fn updates RAM usage
server.runScript(runningScriptObj, Player);
return Promise.resolve(true);
}
}
}
workerScript.scriptRef.log("Could not find script " + scriptname + " on " + server.hostname);
return Promise.resolve(false);
}
export function getErrorLineNumber(exp, workerScript) {
var code = workerScript.scriptRef.codeCode();

File diff suppressed because it is too large Load Diff

@ -1,3 +1,9 @@
/**
* Functions for handling WorkerScripts, which are the underlying mechanism
* that allows for scripts to run
*/
import { WorkerScript } from "./Netscript/WorkerScript";
import {
addActiveScriptsItem,
deleteActiveScriptsItem,
@ -6,9 +12,7 @@ import {
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Interpreter } from "./JSInterpreter";
import { Environment } from "./NetscriptEnvironment";
import {
evaluate,
isScriptErrorMessage,
makeRuntimeRejectMsg,
killNetscriptDelay
@ -16,6 +20,11 @@ import {
import { NetscriptFunctions } from "./NetscriptFunctions";
import { executeJSScript } from "./NetscriptJSEvaluator";
import { NetscriptPort } from "./NetscriptPort";
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
import {
findRunningScript,
scriptCalculateOfflineProduction,
} from "./Script/ScriptHelpers";
import { AllServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
@ -29,79 +38,17 @@ import { arrayToString } from "../utils/helpers/arrayToString";
import { roundToTwo } from "../utils/helpers/roundToTwo";
import { isString } from "../utils/StringHelperFunctions";
const walk = require("acorn/dist/walk");
function WorkerScript(runningScriptObj) {
this.name = runningScriptObj.filename;
this.running = false;
this.serverIp = runningScriptObj.server;
this.code = runningScriptObj.getCode();
this.env = new Environment(this);
this.env.set("args", runningScriptObj.args.slice());
this.output = "";
this.ramUsage = 0;
this.scriptRef = runningScriptObj;
this.errorMessage = "";
this.args = runningScriptObj.args.slice();
this.delay = null;
this.fnWorker = null; //Workerscript for a function call
this.checkingRam = false;
this.loadedFns = {}; //Stores names of fns that are "loaded" by this script, thus using RAM. Used for static RAM evaluation
this.disableLogs = {}; //Stores names of fns that should have logs disabled
//Properties used for dynamic RAM evaluation
this.dynamicRamUsage = CONSTANTS.ScriptBaseRamCost;
this.dynamicLoadedFns = {};
}
//Returns the server on which the workerScript is running
WorkerScript.prototype.getServer = function() {
return AllServers[this.serverIp];
}
//Returns the Script object for the underlying script
WorkerScript.prototype.getScript = function() {
let server = this.getServer();
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.name) {
return server.scripts[i];
}
}
console.log("ERROR: Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong");
return null;
}
//Returns the Script object for the specified script
WorkerScript.prototype.getScriptOnServer = function(fn, server) {
if (server == null) {
server = this.getServer();
}
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === fn) {
return server.scripts[i];
}
}
return null;
}
WorkerScript.prototype.shouldLog = function(fn) {
return (this.disableLogs.ALL == null && this.disableLogs[fn] == null);
}
WorkerScript.prototype.log = function(txt) {
this.scriptRef.log(txt);
}
//Array containing all scripts that are running across all servers, to easily run them all
let workerScripts = [];
export const workerScripts = [];
var NetscriptPorts = [];
export const NetscriptPorts = [];
for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
NetscriptPorts.push(new NetscriptPort());
}
function prestigeWorkerScripts() {
export function prestigeWorkerScripts() {
for (var i = 0; i < workerScripts.length; ++i) {
deleteActiveScriptsItem(workerScripts[i]);
workerScripts[i].env.stopFlag = true;
@ -457,7 +404,7 @@ function processNetscript1Imports(code, workerScript) {
}
//Loop through workerScripts and run every script that is not currently running
function runScriptsLoop() {
export function runScriptsLoop() {
var scriptDeleted = false;
//Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing
@ -563,30 +510,28 @@ function runScriptsLoop() {
setTimeoutRef(runScriptsLoop, 6000);
}
//Queues a script to be killed by settings its stop flag to true. Then, the code will reject
//all of its promises recursively, and when it does so it will no longer be running.
//The runScriptsLoop() will then delete the script from worker scripts
function killWorkerScript(runningScriptObj, serverIp) {
/**
* Queues a script to be killed by setting its stop flag to true. This
* kills and timed/blocking Netscript functions (like hack(), sleep(), etc.) and
* prevents any further execution of Netscript functions.
* The runScriptsLoop() handles the actual deletion of the WorkerScript
*/
export function killWorkerScript(runningScriptObj, serverIp) {
for (var i = 0; i < workerScripts.length; i++) {
if (workerScripts[i].name == runningScriptObj.filename && workerScripts[i].serverIp == serverIp &&
compareArrays(workerScripts[i].args, runningScriptObj.args)) {
workerScripts[i].env.stopFlag = true;
killNetscriptDelay(workerScripts[i]);
//Recursively kill all functions
var curr = workerScripts[i];
while (curr.fnWorker) {
curr.fnWorker.env.stopFlag = true;
killNetscriptDelay(curr.fnWorker);
curr = curr.fnWorker;
}
return true;
}
}
return false;
}
//Queues a script to be run
function addWorkerScript(runningScriptObj, server) {
/**
* Given a RunningScript object, queues that script to be run
*/
export function addWorkerScript(runningScriptObj, server) {
var filename = runningScriptObj.filename;
//Update server's ram usage
@ -596,7 +541,7 @@ function addWorkerScript(runningScriptObj, server) {
} else {
runningScriptObj.threads = 1;
}
var ramUsage = roundToTwo(runningScriptObj.getRamUsage() * threads);
var ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads);
var ramAvailable = server.maxRam - server.ramUsed;
if (ramUsage > ramAvailable) {
dialogBoxCreate("Not enough RAM to run script " + runningScriptObj.filename + " with args " +
@ -608,7 +553,7 @@ function addWorkerScript(runningScriptObj, server) {
server.ramUsed = roundToTwo(server.ramUsed + ramUsage);
//Create the WorkerScript
var s = new WorkerScript(runningScriptObj);
var s = new WorkerScript(runningScriptObj, NetscriptFunctions);
s.ramUsage = ramUsage;
//Add the WorkerScript to the Active Scripts list
@ -619,14 +564,105 @@ function addWorkerScript(runningScriptObj, server) {
return;
}
//Updates the online running time stat of all running scripts
function updateOnlineScriptTimes(numCycles = 1) {
/**
* Updates the online running time stat of all running scripts
*/
export function updateOnlineScriptTimes(numCycles = 1) {
var time = (numCycles * Engine._idleSpeed) / 1000; //seconds
for (var i = 0; i < workerScripts.length; ++i) {
workerScripts[i].scriptRef.onlineRunningTime += time;
}
}
export {WorkerScript, workerScripts, NetscriptPorts, runScriptsLoop,
killWorkerScript, addWorkerScript, updateOnlineScriptTimes,
prestigeWorkerScripts};
/**
* Called when the game is loaded. Loads all running scripts (from all servers)
* into worker scripts so that they will start running
*/
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"); }
for (const property in AllServers) {
if (AllServers.hasOwnProperty(property)) {
const server = AllServers[property];
// Reset each server's RAM usage to 0
server.ramUsed = 0;
// Reset modules on all scripts
for (let i = 0; i < server.scripts.length; ++i) {
server.scripts[i].module = "";
}
if (skipScriptLoad) {
// Start game with no scripts
server.runningScripts.length = 0;
} else {
for (let j = 0; j < server.runningScripts.length; ++j) {
addWorkerScript(server.runningScripts[j], server);
// Offline production
total += scriptCalculateOfflineProduction(server.runningScripts[j]);
}
}
}
}
return total;
}
/**
* Run a script from inside another script (run(), exec(), spawn(), etc.)
*/
export function runScriptFromScript(server, scriptname, args, workerScript, threads=1) {
//Check if the script is already running
let runningScriptObj = findRunningScript(scriptname, args, server);
if (runningScriptObj != null) {
workerScript.scriptRef.log(scriptname + " is already running on " + server.hostname);
return Promise.resolve(false);
}
//'null/undefined' arguments are not allowed
for (let i = 0; i < args.length; ++i) {
if (args[i] == null) {
workerScript.scriptRef.log("ERROR: Cannot execute a script with null/undefined as an argument");
return Promise.resolve(false);
}
}
//Check if the script exists and if it does run it
for (var i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename == scriptname) {
//Check for admin rights and that there is enough RAM availble to run
var script = server.scripts[i];
var ramUsage = script.ramUsage;
threads = Math.round(Number(threads)); //Convert to number and round
if (threads === 0) { return Promise.resolve(false); }
ramUsage = ramUsage * threads;
var ramAvailable = server.maxRam - server.ramUsed;
if (server.hasAdminRights == false) {
workerScript.scriptRef.log("Cannot run script " + scriptname + " on " + server.hostname + " because you do not have root access!");
return Promise.resolve(false);
} else if (ramUsage > ramAvailable){
workerScript.scriptRef.log("Cannot run script " + scriptname + "(t=" + threads + ") on " + server.hostname + " because there is not enough available RAM!");
return Promise.resolve(false);
} else {
//Able to run script
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...`);
}
let runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads;
addWorkerScript(runningScriptObj, server);
// Push onto runningScripts.
// This has to come after addWorkerScript() because that fn updates RAM usage
server.runScript(runningScriptObj, Player.hacknet_node_money_mult);
return Promise.resolve(true);
}
}
}
workerScript.scriptRef.log("Could not find script " + scriptname + " on " + server.hostname);
return Promise.resolve(false);
}

@ -51,6 +51,7 @@ export interface IPlayer {
purchasedServers: any[];
queuedAugmentations: IPlayerOwnedAugmentation[];
resleeves: Resleeve[];
scriptProdSinceLastAug: number;
sleeves: Sleeve[];
sleevesFromCovenant: number;
sourceFiles: IPlayerOwnedSourceFile[];

@ -27,11 +27,13 @@ import { Cities } from "../../Locations/Cities";
import { Locations } from "../../Locations/Locations";
import { CityName } from "../../Locations/data/CityNames";
import { LocationName } from "../../Locations/data/LocationNames";
import {hasBn11SF, hasWallStreetSF,hasAISF} from "../../NetscriptFunctions";
import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve";
import { AllServers,
AddToAllServers } from "../../Server/AllServers";
import { Server } from "../../Server/Server";
import {
AllServers,
AddToAllServers,
createUniqueRandomIp,
} from "../../Server/AllServers";
import { safetlyCreateUniqueServer } from "../../Server/ServerHelpers";
import { Settings } from "../../Settings/Settings";
import { SpecialServerIps, SpecialServerNames } from "../../Server/SpecialServerIps";
import { SourceFiles, applySourceFile } from "../../SourceFile";
@ -43,18 +45,25 @@ import {numeralWrapper} from "../../ui/numeralFormat";
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { clearEventListeners } from "../../../utils/uiHelpers/clearEventListeners";
import {createRandomIp} from "../../../utils/IPAddress";
import {Reviver, Generic_toJSON,
Generic_fromJSON} from "../../../utils/JSONReviver";
import {
Reviver,
Generic_toJSON,
Generic_fromJSON,
} from "../../../utils/JSONReviver";
import {convertTimeMsToTimeElapsedString} from "../../../utils/StringHelperFunctions";
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
export function init() {
/* Initialize Player's home computer */
var t_homeComp = new Server({
ip:createRandomIp(), hostname:"home", organizationName:"Home PC",
isConnectedTo:true, adminRights:true, purchasedByPlayer:true, maxRam:8
var t_homeComp = safetlyCreateUniqueServer({
adminRights: true,
hostname: "home",
ip: createUniqueRandomIp(),
isConnectedTo: true,
maxRam: 8,
organizationName: "Home PC",
purchasedByPlayer: true,
});
this.homeComputer = t_homeComp.ip;
this.currentServer = t_homeComp.ip;
@ -150,7 +159,7 @@ export function prestigeAugmentation() {
this.moneySourceA.reset();
this.hacknetNodes.length = 0;
this.hashManager.prestige(this);
this.hashManager.prestige();
// Re-calculate skills and reset HP
this.updateSkillLevels();
@ -240,7 +249,7 @@ export function prestigeSourceFile() {
this.lastUpdate = new Date().getTime();
this.hacknetNodes.length = 0;
this.hashManager.prestige(this);
this.hashManager.prestige();
// Gang
this.gang = null;
@ -454,7 +463,7 @@ export function gainIntelligenceExp(exp) {
if (isNaN(exp)) {
console.log("ERROR: NaN passed into Player.gainIntelligenceExp()"); return;
}
if (hasAISF || this.intelligence > 0) {
if (SourceFileFlags[5] > 0 || this.intelligence > 0) {
this.intelligence_exp += exp;
}
}
@ -943,7 +952,7 @@ export function getWorkMoneyGain() {
// If player has SF-11, calculate salary multiplier from favor
let bn11Mult = 1;
const company = Companies[this.companyName];
if (hasBn11SF) { bn11Mult = 1 + (company.favor / 100); }
if (SourceFileFlags[11] > 0) { bn11Mult = 1 + (company.favor / 100); }
// Get base salary
const companyPositionName = this.jobs[this.companyName];

@ -7,8 +7,11 @@ import { CONSTANTS } from "../../Constants";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { HacknetServer } from "../../Hacknet/HacknetServer";
import { AddToAllServers,
AllServers } from "../../Server/AllServers";
import {
AddToAllServers,
AllServers,
createUniqueRandomIp,
} from "../../Server/AllServers";
import { SpecialServerIps } from "../../Server/SpecialServerIps";
export function hasTorRouter(this: IPlayer) {
@ -41,7 +44,8 @@ export function createHacknetServer(this: IPlayer): HacknetServer {
const server = new HacknetServer({
adminRights: true,
hostname: name,
player: this,
ip: createUniqueRandomIp(),
// player: this,
});
this.hacknetNodes.push(server.ip);

@ -14,8 +14,6 @@ import { Person,
createTaskTracker } from "../Person";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";

3
src/Player.d.ts vendored Normal file

@ -0,0 +1,3 @@
import { IPlayer } from "./PersonObjects/IPlayer";
export declare let Player: IPlayer;

@ -16,14 +16,10 @@ import { Faction } from "./Faction/Faction";
import { Factions, initFactions } from "./Faction/Factions";
import { joinFaction } from "./Faction/FactionHelpers";
import { deleteGangDisplayContent } from "./Gang";
import { updateHashManagerCapacity } from "./Hacknet/HacknetHelpers";
import { Message } from "./Message/Message";
import { initMessages, Messages } from "./Message/MessageHelpers";
import { initSingularitySFFlags, hasWallStreetSF } from "./NetscriptFunctions";
import {
WorkerScript,
workerScripts,
prestigeWorkerScripts
} from "./NetscriptWorker";
import { prestigeWorkerScripts } from "./NetscriptWorker";
import { Player } from "./Player";
import {
@ -152,7 +148,7 @@ function prestigeAugmentation() {
// BitNode 8: Ghost of Wall Street
if (Player.bitNodeN === 8) {Player.money = new Decimal(BitNode8StartingMoney);}
if (Player.bitNodeN === 8 || hasWallStreetSF) {
if (Player.bitNodeN === 8 || SourceFileFlags[8] > 0) {
Player.hasWseAccount = true;
Player.hasTixApiAccess = true;
}
@ -257,9 +253,6 @@ function prestigeSourceFile() {
Terminal.resetTerminalInput();
Engine.loadTerminalContent();
// Reinitialize Bit Node flags
initSingularitySFFlags();
// BitNode 3: Corporatocracy
if (Player.bitNodeN === 3) {
homeComp.messages.push("corporation-management-handbook.lit");
@ -313,7 +306,7 @@ function prestigeSourceFile() {
// BitNode 8: Ghost of Wall Street
if (Player.bitNodeN === 8) {Player.money = new Decimal(BitNode8StartingMoney);}
if (Player.bitNodeN === 8 || hasWallStreetSF) {
if (Player.bitNodeN === 8 || SourceFileFlags[8] > 0) {
Player.hasWseAccount = true;
Player.hasTixApiAccess = true;
}
@ -345,7 +338,7 @@ function prestigeSourceFile() {
hserver.cache = 5;
hserver.updateHashRate(Player);
hserver.updateHashCapacity();
Player.hashManager.updateCapacity(Player);
updateHashManagerCapacity();
}
// Refresh Main Menu (the 'World' menu, specifically)

@ -1,10 +1,7 @@
// 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 { RamCosts, RamCostConstants } from "../Netscript/RamCostGenerator";
import { parse, Node } from "../../utils/acorn";
// These special strings are used to reference the presence of a given logical
@ -90,7 +87,7 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
// 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;
let ram = RamCostConstants.ScriptBaseRamCost;
const unresolvedRefs = Object.keys(dependencyMap).filter(s => s.startsWith(initialModule));
const resolvedRefs = new Set();
while (unresolvedRefs.length > 0) {
@ -98,13 +95,13 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
// 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;
ram += RamCostConstants.ScriptHacknetNodesRamCost;
}
if (ref === "document" && !resolvedRefs.has("document")) {
ram += CONSTANTS.ScriptDomRamCost;
ram += RamCostConstants.ScriptDomRamCost;
}
if (ref === "window" && !resolvedRefs.has("window")) {
ram += CONSTANTS.ScriptDomRamCost;
ram += RamCostConstants.ScriptDomRamCost;
}
resolvedRefs.add(ref);
@ -124,9 +121,8 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
}
}
// 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.
// Check if this identifier is a function in the workerscript env.
// If it is, then we need to get its RAM cost.
//
// TODO it would be simpler to just reference a dictionary.
try {
@ -152,8 +148,15 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
}
}
//Special logic for namespaces (Bladeburner, CodingCOntract)
var func;
// Only count each function once
if (workerScript.loadedFns[ref]) {
continue;
} else {
workerScript.loadedFns[ref] = true;
}
// This accounts for namespaces (Bladeburner, CodingCOntract)
let func;
if (ref in workerScript.env.vars.bladeburner) {
func = workerScript.env.vars.bladeburner[ref];
} else if (ref in workerScript.env.vars.codingcontract) {
@ -163,7 +166,7 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
} else if (ref in workerScript.env.vars.sleeve) {
func = workerScript.env.vars.sleeve[ref];
} else {
func = workerScript.env.get(ref);
func = workerScript.env.vars[ref];
}
ram += applyFuncRam(func);
} catch (error) {continue;}
@ -309,103 +312,23 @@ function parseOnlyCalculateDeps(code, currentModule) {
}
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;
// We don't need a real WorkerScript for this. Just an object that keeps
// track of whatever's needed for RAM calculations
const workerScript = {
loadedFns: {},
serverIp: currServ.ip,
env: {
vars: RamCosts,
}
}
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) {
console.error(`Failed to parse script for RAM calculations:`);
console.error(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;
return -1;
}

@ -2,14 +2,15 @@
// A Script can have multiple active instances
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,
import {
Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
Reviver
} from "../../utils/JSONReviver";
import { getTimestamp } from "../../utils/helpers/getTimestamp";
export class RunningScript {
@ -73,35 +74,6 @@ export class RunningScript {
this.ramUsage = script.ramUsage;
}
getCode(): string {
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 "";
}
getRamUsage(): number {
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;
}
log(txt: string): void {
if (this.logs.length > Settings.MaxLogCapacity) {
//Delete first element and add new log entry to the end.

@ -0,0 +1,20 @@
import { AllServers } from "../Server/AllServers";
import { RunningScript } from "./RunningScript";
export function getRamUsageFromRunningScript(script: RunningScript): number {
if (script.ramUsage != null && script.ramUsage > 0) {
return script.ramUsage; // Use cached value
}
const server = AllServers[script.server];
if (server == null) { return 0; }
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === script.filename) {
// Cache the ram usage for the next call
script.ramUsage = server.scripts[i].ramUsage;
return script.ramUsage;
}
}
return 0;
}

@ -2,14 +2,14 @@
// 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 { Page, routing } from "../ui/navigationTracking";
import { setTimeoutRef } from "../utils/SetTimeoutRef";
import { Generic_fromJSON,
import {
Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
Reviver
} from "../../utils/JSONReviver";
import { roundToTwo } from "../../utils/helpers/roundToTwo";
export class Script {
@ -64,7 +64,7 @@ export class Script {
}
// Save a script FROM THE SCRIPT EDITOR
saveScript(code: string, p: IPlayer): void {
saveScript(code: string, currServ: string): void {
if (routing.isOn(Page.ScriptEditor)) {
//Update code and filename
this.code = code.replace(/^\s+|\s+$/g, '');
@ -77,7 +77,7 @@ export class Script {
this.filename = filenameElem!.value;
// Server
this.server = p.currentServer;
this.server = currServ;
//Calculate/update ram usage, execution time, etc.
this.updateRamUsage();

@ -7,9 +7,11 @@ 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 { addWorkerScript } from "../NetscriptWorker";
import {
iTutorialSteps,
iTutorialNextStep,
ITutorial
} from "../InteractiveTutorial";
import { Player } from "../Player";
import { AceEditor } from "../ScriptEditor/Ace";
import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror";
@ -24,8 +26,11 @@ 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 {
Reviver,
Generic_toJSON,
Generic_fromJSON
} from "../../utils/JSONReviver";
import { compareArrays } from "../../utils/helpers/compareArrays";
import { createElement } from "../../utils/uiHelpers/createElement";
@ -127,20 +132,22 @@ export function scriptEditorInit() {
editorSelector.onchange = () => {
const opt = editorSelector.value;
switch (opt) {
case EditorSetting.Ace:
case EditorSetting.Ace: {
const codeMirrorCode = CodeMirrorEditor.getCode();
const codeMirrorFn = CodeMirrorEditor.getFilename();
AceEditor.create();
CodeMirrorEditor.setInvisible();
AceEditor.openScript(codeMirrorFn, codeMirrorCode);
break;
case EditorSetting.CodeMirror:
}
case EditorSetting.CodeMirror: {
const aceCode = AceEditor.getCode();
const aceFn = AceEditor.getFilename();
CodeMirrorEditor.create();
AceEditor.setInvisible();
CodeMirrorEditor.openScript(aceFn, aceCode);
break;
}
default:
console.error(`Unrecognized Editor Setting: ${opt}`);
return;
@ -229,7 +236,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(getCurrentEditor().getCode(), Player);
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer);
Engine.loadTerminalContent();
return iTutorialNextStep();
}
@ -237,7 +244,7 @@ function saveAndCloseScriptEditor() {
//If the current script does NOT exist, create a new one
let script = new Script();
script.saveScript(getCurrentEditor().getCode(), Player);
script.saveScript(getCurrentEditor().getCode(), Player.currentServer);
s.scripts.push(script);
return iTutorialNextStep();
@ -265,7 +272,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(getCurrentEditor().getCode(), Player);
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer);
Engine.loadTerminalContent();
return;
}
@ -273,7 +280,7 @@ function saveAndCloseScriptEditor() {
//If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(getCurrentEditor().getCode(), Player);
script.saveScript(getCurrentEditor().getCode(), Player.currentServer);
s.scripts.push(script);
} else if (filename.endsWith(".txt")) {
for (var i = 0; i < s.textFiles.length; ++i) {
@ -293,42 +300,7 @@ function saveAndCloseScriptEditor() {
Engine.loadTerminalContent();
}
//Called when the game is loaded. Loads all running scripts (from all servers)
//into worker scripts so that they will start running
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"); }
for (var property in AllServers) {
if (AllServers.hasOwnProperty(property)) {
var server = AllServers[property];
//Reset each server's RAM usage to 0
server.ramUsed = 0;
//Reset modules on all scripts
for (var i = 0; i < server.scripts.length; ++i) {
server.scripts[i].module = "";
}
if (skipScriptLoad) {
//Start game with no scripts
server.runningScripts.length = 0;
} else {
for (var j = 0; j < server.runningScripts.length; ++j) {
addWorkerScript(server.runningScripts[j], server);
//Offline production
total += scriptCalculateOfflineProduction(server.runningScripts[j]);
}
}
}
}
return total;
}
function scriptCalculateOfflineProduction(runningScriptObj) {
export function scriptCalculateOfflineProduction(runningScriptObj) {
//The Player object stores the last update time from when we were online
var thisUpdate = new Date().getTime();
var lastUpdate = Player.lastUpdate;

@ -5,25 +5,42 @@ import { serverMetadata } from "./data/servers";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { IMap } from "../types";
import { createRandomIp,
ipExists } from "../../utils/IPAddress";
import { createRandomIp } 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
/**
* Map of all Servers that exist in the game
* Key (string) = IP
* Value = Server object
*/
export let AllServers: IMap<Server | HacknetServer> = {};
export function ipExists(ip: string) {
return (AllServers[ip] != null);
}
export function createUniqueRandomIp(): string {
const ip = createRandomIp();
// If the Ip already exists, recurse to create a new one
if (ipExists(ip)) {
return createRandomIp();
}
return ip;
}
// Saftely add a Server to the AllServers map
export function AddToAllServers(server: Server | HacknetServer): 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);
console.warn(`IP of server that's being added: ${serverIp}`);
console.warn(`Hostname of the server thats being added: ${server.hostname}`);
console.warn(`The server that already has this IP is: ${AllServers[serverIp].hostname}`);
throw new Error("Error: Trying to add a server with an existing IP");
}
AllServers[serverIp] = server;
}
@ -71,7 +88,7 @@ export function initForeignServers(homeComputer: Server) {
for (const metadata of serverMetadata) {
const serverParams: IServerParams = {
hostname: metadata.hostname,
ip: createRandomIp(),
ip: createUniqueRandomIp(),
numOpenPortsRequired: metadata.numOpenPortsRequired,
organizationName: metadata.organizationName
};

@ -1,9 +1,6 @@
// Class representing a single hackable Server
import { BaseServer } from "./BaseServer";
// TODO This import is a circular import. Try to fix it in the future
import { GetServerByHostname } from "./ServerHelpers";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { createRandomString } from "../utils/helpers/createRandomString";
@ -12,7 +9,7 @@ import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
interface IConstructorParams {
export interface IConstructorParams {
adminRights?: boolean;
hackDifficulty?: number;
hostname: string;
@ -77,17 +74,6 @@ export class Server extends BaseServer {
this.hostname = createRandomString(10);
}
// Validate hostname by ensuring there are no repeats
if (GetServerByHostname(this.hostname) != null) {
// Use a for loop to ensure that we don't get suck in an infinite loop somehow
let hostname: string = this.hostname;
for (let i = 0; i < 200; ++i) {
hostname = `${this.hostname}-${i}`;
if (GetServerByHostname(hostname) == null) { break; }
}
this.hostname = hostname;
}
this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false;
//RAM, CPU speed and Scripts

@ -1,5 +1,9 @@
import { AllServers } from "./AllServers";
import { Server } from "./Server";
import {
AllServers,
createUniqueRandomIp,
ipExists,
} from "./AllServers";
import { Server, IConstructorParams } from "./Server";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
@ -9,6 +13,28 @@ import { Programs } from "../Programs/Programs";
import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress";
/**
* Constructs a new server, while also ensuring that the new server
* does not have a duplicate hostname/ip.
*/
export function safetlyCreateUniqueServer(params: IConstructorParams): Server {
if (params.ip != null && ipExists(params.ip)) {
params.ip = createUniqueRandomIp();
}
if (GetServerByHostname(params.hostname) != null) {
// Use a for loop to ensure that we don't get suck in an infinite loop somehow
let hostname: string = params.hostname;
for (let i = 0; i < 200; ++i) {
hostname = `${params.hostname}-${i}`;
if (GetServerByHostname(hostname) == null) { break; }
}
params.hostname = hostname;
}
return new Server(params);
}
// 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: Server, growth: number, p: IPlayer) {

@ -2,13 +2,17 @@
* Implements functions for purchasing servers or purchasing more RAM for
* the home computer
*/
import {
AddToAllServers,
createUniqueRandomIp,
} from "./AllServers";
import { safetlyCreateUniqueServer } from "./ServerHelpers";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
import { AddToAllServers } from "../Server/AllServers";
import { Server } from "../Server/Server";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { createRandomIp } from "../../utils/IPAddress";
import { yesNoTxtInpBoxGetInput } from "../../utils/YesNoBox";
import { isPowerOfTwo } from "../../utils/helpers/isPowerOfTwo";
@ -67,9 +71,14 @@ export function purchaseServer(ram: number, p: IPlayer) {
}
// Create server
var newServ = new Server({
ip:createRandomIp(), hostname:hostname, organizationName:"",
isConnectedTo:false, adminRights:true, purchasedByPlayer:true, maxRam:ram
const newServ = safetlyCreateUniqueServer({
adminRights: true,
hostname: hostname,
ip: createUniqueRandomIp(),
isConnectedTo: false,
maxRam:ram,
organizationName: "",
purchasedByPlayer: true,
});
AddToAllServers(newServ);

@ -4,7 +4,7 @@
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
export const SourceFileFlags: number[] = Array(CONSTANTS.TotalNumBitNodes + 1); // Skip 0
export const SourceFileFlags: number[] = Array(CONSTANTS.TotalNumBitNodes + 1); // Skip index 0
export function updateSourceFileFlags(p: IPlayer) {
for (let i = 0; i < SourceFileFlags.length; ++i) {

@ -0,0 +1,296 @@
/**
* Functions for buying/selling stocks. There are four functions total, two for
* long positions and two for short positions.
*/
import { Stock } from "./Stock";
import {
getBuyTransactionCost,
getSellTransactionGain,
processBuyTransactionPriceMovement,
processSellTransactionPriceMovement
} from "./StockMarketHelpers";
import { PositionTypes } from "./data/PositionTypes";
import { CONSTANTS } from "../Constants";
import { WorkerScript } from "../Netscript/WorkerScript";
import { Player } from "../Player";
import { numeralWrapper } from "../ui/numeralFormat";
import { dialogBoxCreate } from "../../utils/DialogBox";
/**
* Each function takes an optional config object as its last argument
*/
interface IOptions {
rerenderFn?: () => void;
suppressDialog?: boolean;
}
/**
* Attempt to buy a stock in the long position
* @param {Stock} stock - Stock to buy
* @param {number} shares - Number of shares to buy
* @param {WorkerScript} workerScript - If this is being called through Netscript
* @param opts - Optional configuration for this function's behavior. See top of file
* @returns {boolean} - true if successful, false otherwise
*/
export function buyStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
const tixApi = (workerScript instanceof WorkerScript);
// Validate arguments
shares = Math.round(shares);
if (shares == 0 || shares < 0) { return false; }
if (stock == null || isNaN(shares)) {
if (tixApi) {
workerScript!.log(`ERROR: buyStock() failed due to invalid arguments`);
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer");
}
return false;
}
// Does player have enough money?
const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Long);
if (totalPrice == null) { return false; }
if (Player.money.lt(totalPrice)) {
if (tixApi) {
workerScript!.log(`ERROR: buyStock() failed because you do not have enough money to purchase this potiion. You need ${numeralWrapper.formatMoney(totalPrice)}`);
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
dialogBoxCreate(`You do not have enough money to purchase this. You need ${numeralWrapper.formatMoney(totalPrice)}`);
}
return false;
}
// Would this purchase exceed the maximum number of shares?
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
if (tixApi) {
workerScript!.log(`ERROR: buyStock() failed because purchasing this many shares would exceed ${stock.symbol}'s maximum number of shares`);
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${numeralWrapper.formatBigNumber(stock.maxShares)} shares.`);
}
return false;
}
const origTotal = stock.playerShares * stock.playerAvgPx;
Player.loseMoney(totalPrice);
const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission;
stock.playerShares = Math.round(stock.playerShares + shares);
stock.playerAvgPx = newTotal / stock.playerShares;
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Long);
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
opts.rerenderFn();
}
const resultTxt = `Bought ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol} for ${numeralWrapper.formatMoney(totalPrice)}. ` +
`Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} in commission fees.`
if (tixApi) {
if (workerScript!.shouldLog("buyStock")) { workerScript!.log(resultTxt); }
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
dialogBoxCreate(resultTxt);
}
return true;
}
/**
* Attempt to sell a stock in the long position
* @param {Stock} stock - Stock to sell
* @param {number} shares - Number of shares to sell
* @param {WorkerScript} workerScript - If this is being called through Netscript
* @param opts - Optional configuration for this function's behavior. See top of file
* returns {boolean} - true if successfully sells given number of shares OR MAX owned, false otherwise
*/
export function sellStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
const tixApi = (workerScript instanceof WorkerScript);
// Sanitize/Validate arguments
if (stock == null || shares < 0 || isNaN(shares)) {
if (tixApi) {
workerScript!.log(`ERROR: sellStock() failed due to invalid arguments`);
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
dialogBoxCreate("Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer");
}
return false;
}
shares = Math.round(shares);
if (shares > stock.playerShares) {shares = stock.playerShares;}
if (shares === 0) {return false;}
const gains = getSellTransactionGain(stock, shares, PositionTypes.Long);
if (gains == null) { return false; }
let netProfit = gains - (stock.playerAvgPx * shares);
if (isNaN(netProfit)) { netProfit = 0; }
Player.gainMoney(gains);
Player.recordMoneySource(netProfit, "stock");
if (tixApi) {
workerScript!.scriptRef.onlineMoneyMade += netProfit;
Player.scriptProdSinceLastAug += netProfit;
}
stock.playerShares = Math.round(stock.playerShares - shares);
if (stock.playerShares === 0) {
stock.playerAvgPx = 0;
}
processSellTransactionPriceMovement(stock, shares, PositionTypes.Long);
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
opts.rerenderFn();
}
const resultTxt = `Sold ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol}. ` +
`After commissions, you gained a total of ${numeralWrapper.formatMoney(gains)}.`;
if (tixApi) {
if (workerScript!.shouldLog("sellStock")) { workerScript!.log(resultTxt); }
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
dialogBoxCreate(resultTxt);
}
return true;
}
/**
* Attempt to buy a stock in the short position
* @param {Stock} stock - Stock to sell
* @param {number} shares - Number of shares to short
* @param {WorkerScript} workerScript - If this is being called through Netscript
* @param opts - Optional configuration for this function's behavior. See top of file
* @returns {boolean} - true if successful, false otherwise
*/
export function shortStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
const tixApi = (workerScript instanceof WorkerScript);
// Validate arguments
shares = Math.round(shares);
if (shares === 0 || shares < 0) { return false; }
if (stock == null || isNaN(shares)) {
if (tixApi) {
workerScript!.log("ERROR: shortStock() failed because of invalid arguments.");
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
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");
}
return false;
}
// Does the player have enough money?
const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Short);
if (totalPrice == null) { return false; }
if (Player.money.lt(totalPrice)) {
if (tixApi) {
workerScript!.log("ERROR: shortStock() failed because you do not have enough " +
"money to purchase this short position. You need " +
numeralWrapper.formatMoney(totalPrice));
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
dialogBoxCreate("You do not have enough money to purchase this short position. You need " +
numeralWrapper.formatMoney(totalPrice));
}
return false;
}
// Would this purchase exceed the maximum number of shares?
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
if (tixApi) {
workerScript!.log(`ERROR: shortStock() failed because purchasing this many short shares would exceed ${stock.symbol}'s maximum number of shares.`);
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${stock.maxShares} shares.`);
}
return false;
}
const origTotal = stock.playerShortShares * stock.playerAvgShortPx;
Player.loseMoney(totalPrice);
const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission;
stock.playerShortShares = Math.round(stock.playerShortShares + shares);
stock.playerAvgShortPx = newTotal / stock.playerShortShares;
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Short);
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
opts.rerenderFn();
}
const resultTxt = `Bought a short position of ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol} ` +
`for ${numeralWrapper.formatMoney(totalPrice)}. Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} ` +
`in commission fees.`;
if (tixApi) {
if (workerScript!.shouldLog("shortStock")) { workerScript!.log(resultTxt); }
} else if (!opts.suppressDialog) {
dialogBoxCreate(resultTxt);
}
return true;
}
/**
* Attempt to sell a stock in the short position
* @param {Stock} stock - Stock to sell
* @param {number} shares - Number of shares to sell
* @param {WorkerScript} workerScript - If this is being called through Netscript
* @param opts - Optional configuration for this function's behavior. See top of file
* @returns {boolean} true if successfully sells given amount OR max owned, false otherwise
*/
export function sellShort(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean {
const tixApi = (workerScript instanceof WorkerScript);
if (stock == null || isNaN(shares) || shares < 0) {
if (tixApi) {
workerScript!.log("ERROR: sellShort() failed because of invalid arguments.");
} else if (!opts.suppressDialog) {
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");
}
return false;
}
shares = Math.round(shares);
if (shares > stock.playerShortShares) {shares = stock.playerShortShares;}
if (shares === 0) {return false;}
const origCost = shares * stock.playerAvgShortPx;
const totalGain = getSellTransactionGain(stock, shares, PositionTypes.Short);
if (totalGain == null || isNaN(totalGain) || origCost == null) {
if (tixApi) {
workerScript!.log(`Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`);
} else if (!opts.suppressDialog) {
dialogBoxCreate(`Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`);
}
return false;
}
let profit = totalGain - origCost;
if (isNaN(profit)) { profit = 0; }
Player.gainMoney(totalGain);
Player.recordMoneySource(profit, "stock");
if (tixApi) {
workerScript!.scriptRef.onlineMoneyMade += profit;
Player.scriptProdSinceLastAug += profit;
}
stock.playerShortShares = Math.round(stock.playerShortShares - shares);
if (stock.playerShortShares === 0) {
stock.playerAvgShortPx = 0;
}
processSellTransactionPriceMovement(stock, shares, PositionTypes.Short);
if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") {
opts.rerenderFn();
}
const resultTxt = `Sold your short position of ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol}. ` +
`After commissions, you gained a total of ${numeralWrapper.formatMoney(totalGain)}`;
if (tixApi) {
if (workerScript!.shouldLog("sellShort")) { workerScript!.log(resultTxt); }
} else if (!opts.suppressDialog) {
dialogBoxCreate(resultTxt);
}
return true;
}

@ -22,11 +22,26 @@ export class Order {
readonly pos: PositionTypes;
readonly price: number;
readonly shares: number;
shares: number;
readonly stock: Stock;
readonly type: OrderTypes;
constructor(stk: Stock = new Stock(), shares: number=0, price: number=0, typ: OrderTypes=OrderTypes.LimitBuy, pos: PositionTypes=PositionTypes.Long) {
// Validate arguments
let invalidArgs: boolean = false;
if (typeof shares !== "number" || typeof price !== "number") {
invalidArgs = true;
}
if (isNaN(shares) || isNaN(price)) {
invalidArgs = true;
}
if (!(stk instanceof Stock)) {
invalidArgs = true;
}
if (invalidArgs) {
throw new Error(`Invalid constructor paramters for Order`);
}
this.stock = stk;
this.shares = shares;
this.price = price;

@ -0,0 +1,252 @@
/**
* Helper functions for determine whether Limit and Stop orders should
* be executed (and executing them)
*/
import {
buyStock,
sellStock,
shortStock,
sellShort,
} from "./BuyingAndSelling";
import { IOrderBook } from "./IOrderBook";
import { IStockMarket } from "./IStockMarket";
import { Order } from "./Order";
import { Stock } from "./Stock";
import { OrderTypes } from "./data/OrderTypes";
import { PositionTypes } from "./data/PositionTypes";
import { numeralWrapper } from "../ui/numeralFormat";
import { dialogBoxCreate } from "../../utils/DialogBox";
interface IProcessOrderRefs {
rerenderFn: () => void;
stockMarket: IStockMarket;
}
/**
* Search for all orders of a specific type and execute them if appropriate
* @param {Stock} stock - Stock for which orders should be processed
* @param {OrderTypes} orderType - Type of order to check (Limit/Stop buy/sell)
* @param {PositionTypes} posType - Long or short
* @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function
*/
export function processOrders(stock: Stock, orderType: OrderTypes, posType: PositionTypes, refs: IProcessOrderRefs): void {
let orderBook = refs.stockMarket["Orders"];
if (orderBook == null) {
const orders: IOrderBook = {};
for (const name in refs.stockMarket) {
const stock = refs.stockMarket[name];
if (!(stock instanceof Stock)) { continue; }
orders[stock.symbol] = [];
}
refs.stockMarket["Orders"] = orders;
return; // Newly created, so no orders to process
}
let stockOrders = orderBook[stock.symbol];
if (stockOrders == null || !(stockOrders.constructor === Array)) {
console.error(`Invalid Order book for ${stock.symbol} in processOrders()`);
stockOrders = [];
return;
}
for (const order of stockOrders) {
if (order.type === orderType && order.pos === posType) {
switch (order.type) {
case OrderTypes.LimitBuy:
if (order.pos === PositionTypes.Long && stock.price <= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
}
break;
case OrderTypes.LimitSell:
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
}
break;
case OrderTypes.StopBuy:
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
}
break;
case OrderTypes.StopSell:
if (order.pos === PositionTypes.Long && stock.price <= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
}
break;
default:
console.warn(`Invalid order type: ${order.type}`);
return;
}
}
}
}
/**
* Execute a Stop or Limit Order.
* @param {Order} order - Order being executed
* @param {IStockMarket} stockMarket - Reference to StockMarket object
*/
function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () => void) {
const stock = order.stock;
const orderBook = stockMarket["Orders"];
const stockOrders = orderBook[stock.symbol];
const isLimit = (order.type === OrderTypes.LimitBuy || order.type === OrderTypes.LimitSell);
let limitShares = 0;
// When orders are executed, the buying and selling functions shouldn't
// emit popup dialog boxes. This options object configures the functions for that
const opts = {
rerenderFn: rerenderFn,
suppressDialog: true
}
let res = true;
let isBuy = false;
console.log("Executing the following order:");
console.log(order);
switch (order.type) {
case OrderTypes.LimitBuy: {
isBuy = true;
// We only execute limit orders until the price fails to match the order condition
const isLong = (order.pos === PositionTypes.Long);
const firstShares = Math.min(order.shares, stock.shareTxUntilMovement);
// First transaction to trigger movement
if (isLong ? buyStock(stock, firstShares, null, opts) : shortStock(stock, firstShares, null, opts)) {
limitShares = firstShares;
} else {
break;
}
let remainingShares = order.shares - firstShares;
let remainingIterations = Math.ceil(remainingShares / stock.shareTxForMovement);
for (let i = 0; i < remainingIterations; ++i) {
if (isLong && stock.price > order.price) {
break;
} else if (!isLong && stock.price < order.price) {
break;
}
const shares = Math.min(remainingShares, stock.shareTxForMovement);
if (isLong ? buyStock(stock, shares, null, opts) : shortStock(stock, shares, null, opts)) {
limitShares += shares;
remainingShares -= shares;
} else {
break;
}
}
}
case OrderTypes.StopBuy: {
isBuy = true;
if (order.pos === PositionTypes.Long) {
res = buyStock(stock, order.shares, null, opts) && res;
} else if (order.pos === PositionTypes.Short) {
res = shortStock(stock, order.shares, null, opts) && res;
}
break;
}
case OrderTypes.LimitSell: {
// We only execute limit orders until the price fails to match the order condition
const isLong = (order.pos === PositionTypes.Long);
const firstShares = Math.min(order.shares, stock.shareTxUntilMovement);
// First transaction to trigger movement
if (isLong ? sellStock(stock, firstShares, null, opts) : sellShort(stock, firstShares, null, opts)) {
limitShares = firstShares;
} else {
break;
}
let remainingShares = order.shares - firstShares;
let remainingIterations = Math.ceil(remainingShares / stock.shareTxForMovement);
for (let i = 0; i < remainingIterations; ++i) {
if (isLong && stock.price < order.price) {
break;
} else if (!isLong && stock.price > order.price) {
break;
}
const shares = Math.min(remainingShares, stock.shareTxForMovement);
if (isLong ? sellStock(stock, shares, null, opts) : sellShort(stock, shares, null, opts)) {
limitShares += shares;
remainingShares -= shares;
} else {
break;
}
}
}
case OrderTypes.StopSell: {
if (order.pos === PositionTypes.Long) {
res = sellStock(stock, order.shares, null, opts) && res;
} else if (order.pos === PositionTypes.Short) {
res = sellShort(stock, order.shares, null, opts) && res;
}
break;
}
default:
console.warn(`Invalid order type: ${order.type}`);
return;
}
// Position type, for logging/message purposes
const pos = order.pos === PositionTypes.Long ? "Long" : "Short";
if (res) {
if (isLimit) {
} else {
for (let i = 0; i < stockOrders.length; ++i) {
if (order == stockOrders[i]) {
}
}
}
for (let i = 0; i < stockOrders.length; ++i) {
if (order == stockOrders[i]) {
if (isLimit) {
// Limit orders might only transact a certain # of shares, so we have the adjust the order qty.
stockOrders[i].shares -= limitShares;
if (stockOrders[i].shares <= 0) {
stockOrders.splice(i, 1);
dialogBoxCreate(`${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was filled ` +
`(${Math.round(limitShares)} shares`);
} else {
dialogBoxCreate(`${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was partially filled ` +
`(${Math.round(limitShares)} shares transacted, ${stockOrders[i].shares} shares remaining`);
}
} else {
// Stop orders will transact everything, so they can be removed completely
stockOrders.splice(i, 1);
dialogBoxCreate(`${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was filled ` +
`(${Math.round(order.shares)} shares transacted)`);
}
rerenderFn();
return;
}
}
console.error("Could not find the following Order in Order Book: ");
console.error(order);
} else {
if (isBuy) {
dialogBoxCreate(`Failed to execute ${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` +
`This is most likely because you do not have enough money or the order would exceed the stock's maximum number of shares`);
} else {
dialogBoxCreate(`Failed to execute ${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` +
`This is most likely a bug, please report to game developer with details.`);
}
}
}

@ -1,4 +1,11 @@
import {
buyStock,
sellStock,
shortStock,
sellShort,
} from "./BuyingAndSelling";
import { Order } from "./Order";
import { processOrders } from "./OrderProcessing";
import { Stock } from "./Stock";
import {
getBuyTransactionCost,
@ -17,7 +24,7 @@ import { StockSymbols } from "./data/StockSymbols";
import { StockMarketRoot } from "./ui/Root";
import { CONSTANTS } from "../Constants";
import { WorkerScript } from "../NetscriptWorker";
import { WorkerScript } from "../Netscript/WorkerScript";
import { Player } from "../Player";
import { Page, routing } from ".././ui/navigationTracking";
@ -29,10 +36,12 @@ import { Reviver } from "../../utils/JSONReviver";
import React from "react";
import ReactDOM from "react-dom";
export let StockMarket = {}; // Maps full stock name -> Stock object
export let SymbolToStockMap = {}; // Maps symbol -> Stock object
export function placeOrder(stock, shares, price, type, position, workerScript=null) {
var tixApi = (workerScript instanceof WorkerScript);
var order = new Order(stock, shares, price, type, position);
if (isNaN(shares) || isNaN(price)) {
const tixApi = (workerScript instanceof WorkerScript);
if (typeof shares !== "number" || typeof price !== "number") {
if (tixApi) {
workerScript.scriptRef.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
} else {
@ -40,21 +49,27 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu
}
return false;
}
const order = new Order(stock, shares, price, type, position);
if (StockMarket["Orders"] == null) {
const orders = {};
for (const name in StockMarket) {
if (StockMarket.hasOwnProperty(name)) {
const stk = StockMarket[name];
if (!(stk instanceof Stock)) { continue; }
orders[stk.symbol] = [];
}
}
StockMarket["Orders"] = orders;
}
StockMarket["Orders"][stock.symbol].push(order);
// Process to see if it should be executed immediately
processOrders(order.stock, order.type, order.pos);
const processOrderRefs = {
rerenderFn: displayStockMarketContent,
stockMarket: StockMarket,
}
processOrders(order.stock, order.type, order.pos, processOrderRefs);
displayStockMarketContent();
return true;
}
@ -79,7 +94,7 @@ export function cancelOrder(params, workerScript=null) {
// Order properties are passed in. Need to look for the order
var stockOrders = StockMarket["Orders"][params.stock.symbol];
var orderTxt = params.stock.symbol + " - " + params.shares + " @ " +
numeralWrapper.format(params.price, '$0.000a');
numeralWrapper.formatMoney(params.price);
for (var i = 0; i < stockOrders.length; ++i) {
var order = stockOrders[i];
if (params.shares === order.shares &&
@ -102,50 +117,6 @@ export function cancelOrder(params, workerScript=null) {
return false;
}
function executeOrder(order) {
var stock = order.stock;
var orderBook = StockMarket["Orders"];
var stockOrders = orderBook[stock.symbol];
var res = true;
console.log("Executing the following order:");
console.log(order);
switch (order.type) {
case OrderTypes.LimitBuy:
case OrderTypes.StopBuy:
if (order.pos === PositionTypes.Long) {
res = buyStock(order.stock, order.shares) && res;
} else if (order.pos === PositionTypes.Short) {
res = shortStock(order.stock, order.shares) && res;
}
break;
case OrderTypes.LimitSell:
case OrderTypes.StopSell:
if (order.pos === PositionTypes.Long) {
res = sellStock(order.stock, order.shares) && res;
} else if (order.pos === PositionTypes.Short) {
res = sellShort(order.stock, order.shares) && res;
}
break;
}
if (res) {
// Remove order from order book
for (var i = 0; i < stockOrders.length; ++i) {
if (order == stockOrders[i]) {
stockOrders.splice(i, 1);
displayStockMarketContent();
return;
}
}
console.log("ERROR: Could not find the following Order in Order Book: ");
console.log(order);
} else {
console.log("Order failed to execute");
}
}
export let StockMarket = {}; // Maps full stock name -> Stock object
export let SymbolToStockMap = {}; // Maps symbol -> Stock object
export function loadStockMarket(saveString) {
if (saveString === "") {
StockMarket = {};
@ -172,12 +143,10 @@ export function initStockMarket() {
const orders = {};
for (const name in StockMarket) {
if (StockMarket.hasOwnProperty(name)) {
const stock = StockMarket[name];
if (!(stock instanceof Stock)) { continue; }
orders[stock.symbol] = [];
}
}
StockMarket["Orders"] = orders;
StockMarket.storedCycles = 0;
@ -211,252 +180,6 @@ export function stockMarketCycle() {
}
}
/**
* Attempt to buy a stock in the long position
* @param {Stock} stock - Stock to buy
* @param {number} shares - Number of shares to buy
* @param {WorkerScript} workerScript - If this is being called through Netscript
* @returns {boolean} - true if successful, false otherwise
*/
export function buyStock(stock, shares, workerScript=null) {
const tixApi = (workerScript instanceof WorkerScript);
// Validate arguments
shares = Math.round(shares);
if (shares == 0 || shares < 0) { return false; }
if (stock == null || isNaN(shares)) {
if (tixApi) {
workerScript.log(`ERROR: buyStock() failed due to invalid arguments`);
} else {
dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer");
}
return false;
}
// Does player have enough money?
const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Long);
if (Player.money.lt(totalPrice)) {
if (tixApi) {
workerScript.log(`ERROR: buyStock() failed because you do not have enough money to purchase this potiion. You need ${numeralWrapper.formatMoney(totalPrice)}`);
} else {
dialogBoxCreate(`You do not have enough money to purchase this. You need ${numeralWrapper.formatMoney(totalPrice)}`);
}
return false;
}
// Would this purchase exceed the maximum number of shares?
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
if (tixApi) {
workerScript.log(`ERROR: buyStock() failed because purchasing this many shares would exceed ${stock.symbol}'s maximum number of shares`);
} else {
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${numeralWrapper.formatBigNumber(stock.maxShares)} shares.`);
}
return false;
}
const origTotal = stock.playerShares * stock.playerAvgPx;
Player.loseMoney(totalPrice);
const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission;
stock.playerShares = Math.round(stock.playerShares + shares);
stock.playerAvgPx = newTotal / stock.playerShares;
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Long);
displayStockMarketContent();
const resultTxt = `Bought ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol} for ${numeralWrapper.formatMoney(totalPrice)}. ` +
`Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} in commission fees.`
if (tixApi) {
if (workerScript.shouldLog("buyStock")) { workerScript.log(resultTxt); }
} else {
dialogBoxCreate(resultTxt);
}
return true;
}
/**
* Attempt to sell a stock in the long position
* @param {Stock} stock - Stock to sell
* @param {number} shares - Number of shares to sell
* @param {WorkerScript} workerScript - If this is being called through Netscript
* returns {boolean} - true if successfully sells given number of shares OR MAX owned, false otherwise
*/
export function sellStock(stock, shares, workerScript=null) {
const tixApi = (workerScript instanceof WorkerScript);
// Sanitize/Validate arguments
if (stock == null || shares < 0 || isNaN(shares)) {
if (tixApi) {
workerScript.log(`ERROR: sellStock() failed due to invalid arguments`);
} else {
dialogBoxCreate("Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer");
}
return false;
}
shares = Math.round(shares);
if (shares > stock.playerShares) {shares = stock.playerShares;}
if (shares === 0) {return false;}
const gains = getSellTransactionGain(stock, shares, PositionTypes.Long);
let netProfit = gains - (stock.playerAvgPx * shares);
if (isNaN(netProfit)) { netProfit = 0; }
Player.gainMoney(gains);
Player.recordMoneySource(netProfit, "stock");
if (tixApi) {
workerScript.scriptRef.onlineMoneyMade += netProfit;
Player.scriptProdSinceLastAug += netProfit;
}
stock.playerShares = Math.round(stock.playerShares - shares);
if (stock.playerShares === 0) {
stock.playerAvgPx = 0;
}
processSellTransactionPriceMovement(stock, shares, PositionTypes.Long);
displayStockMarketContent();
const resultTxt = `Sold ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol}. ` +
`After commissions, you gained a total of ${numeralWrapper.formatMoney(gains)}.`;
if (tixApi) {
if (workerScript.shouldLog("sellStock")) { workerScript.log(resultTxt); }
} else {
dialogBoxCreate(resultTxt);
}
return true;
}
/**
* Attempt to buy a stock in the short position
* @param {Stock} stock - Stock to sell
* @param {number} shares - Number of shares to short
* @param {WorkerScript} workerScript - If this is being called through Netscript
* @returns {boolean} - true if successful, false otherwise
*/
export function shortStock(stock, shares, workerScript=null) {
const tixApi = (workerScript instanceof WorkerScript);
// Validate arguments
shares = Math.round(shares);
if (shares === 0 || shares < 0) { return false; }
if (stock == null || isNaN(shares)) {
if (tixApi) {
workerScript.log("ERROR: shortStock() failed because of invalid arguments.");
} else {
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");
}
return false;
}
// Does the player have enough money?
const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Short);
if (Player.money.lt(totalPrice)) {
if (tixApi) {
workerScript.log("ERROR: shortStock() failed because you do not have enough " +
"money to purchase this short position. You need " +
numeralWrapper.formatMoney(totalPrice));
} else {
dialogBoxCreate("You do not have enough money to purchase this short position. You need " +
numeralWrapper.formatMoney(totalPrice));
}
return false;
}
// Would this purchase exceed the maximum number of shares?
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
if (tixApi) {
workerScript.log(`ERROR: shortStock() failed because purchasing this many short shares would exceed ${stock.symbol}'s maximum number of shares.`);
} else {
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${stock.maxShares} shares.`);
}
return false;
}
const origTotal = stock.playerShortShares * stock.playerAvgShortPx;
Player.loseMoney(totalPrice);
const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission;
stock.playerShortShares = Math.round(stock.playerShortShares + shares);
stock.playerAvgShortPx = newTotal / stock.playerShortShares;
processBuyTransactionPriceMovement(stock, shares, PositionTypes.Short);
displayStockMarketContent();
const resultTxt = `Bought a short position of ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol} ` +
`for ${numeralWrapper.formatMoney(totalPrice)}. Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} ` +
`in commission fees.`;
if (tixApi) {
if (workerScript.shouldLog("shortStock")) { workerScript.log(resultTxt); }
} else {
dialogBoxCreate(resultTxt);
}
return true;
}
/**
* Attempt to sell a stock in the short position
* @param {Stock} stock - Stock to sell
* @param {number} shares - Number of shares to sell
* @param {WorkerScript} workerScript - If this is being called through Netscript
* @returns {boolean} true if successfully sells given amount OR max owned, false otherwise
*/
export function sellShort(stock, shares, workerScript=null) {
const tixApi = (workerScript instanceof WorkerScript);
if (stock == null || isNaN(shares) || shares < 0) {
if (tixApi) {
workerScript.log("ERROR: sellShort() failed because of invalid arguments.");
} else {
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");
}
return false;
}
shares = Math.round(shares);
if (shares > stock.playerShortShares) {shares = stock.playerShortShares;}
if (shares === 0) {return false;}
const origCost = shares * stock.playerAvgShortPx;
const totalGain = getSellTransactionGain(stock, shares, PositionTypes.Short);
if (totalGain == null || isNaN(totalGain) || origCost == null) {
if (tixApi) {
workerScript.log(`Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`);
} else {
dialogBoxCreate(`Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`);
}
return false;
}
let profit = totalGain - origCost;
if (isNaN(profit)) { profit = 0; }
Player.gainMoney(totalGain);
Player.recordMoneySource(profit, "stock");
if (tixApi) {
workerScript.scriptRef.onlineMoneyMade += profit;
Player.scriptProdSinceLastAug += profit;
}
stock.playerShortShares = Math.round(stock.playerShortShares - shares);
if (stock.playerShortShares === 0) {
stock.playerAvgShortPx = 0;
}
processSellTransactionPriceMovement(stock, shares, PositionTypes.Short);
displayStockMarketContent();
const resultTxt = `Sold your short position of ${numeralWrapper.format(shares, '0,0')} shares of ${stock.symbol}. ` +
`After commissions, you gained a total of ${numeralWrapper.formatMoney(totalGain)}`;
if (tixApi) {
if (workerScript.shouldLog("sellShort")) { workerScript.log(resultTxt); }
} else {
dialogBoxCreate(resultTxt);
}
return true;
}
// Stock prices updated every 6 seconds
const msPerStockUpdate = 6e3;
const cyclesPerStockUpdate = msPerStockUpdate / CONSTANTS.MilliPerCycle;
@ -494,18 +217,22 @@ export function processStockPrices(numCycles=1) {
if (isNaN(chc)) { chc = 0.5; }
const c = Math.random();
const processOrderRefs = {
rerenderFn: displayStockMarketContent,
stockMarket: StockMarket,
}
if (c < chc) {
stock.price *= (1 + av);
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short);
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long);
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long);
processOrders(stock, OrderTypes.StopSell, PositionTypes.Short);
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short, processOrderRefs);
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long, processOrderRefs);
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long, processOrderRefs);
processOrders(stock, OrderTypes.StopSell, PositionTypes.Short, processOrderRefs);
} else {
stock.price /= (1 + av);
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long);
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short);
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short);
processOrders(stock, OrderTypes.StopSell, PositionTypes.Long);
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long, processOrderRefs);
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short, processOrderRefs);
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short, processOrderRefs);
processOrders(stock, OrderTypes.StopSell, PositionTypes.Long, processOrderRefs);
}
let otlkMagChange = stock.otlkMag * av;
@ -530,67 +257,6 @@ export function processStockPrices(numCycles=1) {
displayStockMarketContent();
}
// Checks and triggers any orders for the specified stock
function processOrders(stock, orderType, posType) {
var orderBook = StockMarket["Orders"];
if (orderBook == null) {
var orders = {};
for (var name in StockMarket) {
if (StockMarket.hasOwnProperty(name)) {
var stock = StockMarket[name];
if (!(stock instanceof Stock)) {continue;}
orders[stock.symbol] = [];
}
}
StockMarket["Orders"] = orders;
return; //Newly created, so no orders to process
}
var stockOrders = orderBook[stock.symbol];
if (stockOrders == null || !(stockOrders.constructor === Array)) {
console.log("ERROR: Invalid Order book for " + stock.symbol + " in processOrders()");
stockOrders = [];
return;
}
for (var i = 0; i < stockOrders.length; ++i) {
var order = stockOrders[i];
if (order.type === orderType && order.pos === posType) {
switch(order.type) {
case OrderTypes.LimitBuy:
if (order.pos === PositionTypes.Long && stock.price <= order.price) {
executeOrder/*66*/(order);
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
executeOrder/*66*/(order);
}
break;
case OrderTypes.LimitSell:
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
executeOrder/*66*/(order);
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
executeOrder/*66*/(order);
}
break;
case OrderTypes.StopBuy:
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
executeOrder/*66*/(order);
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
executeOrder/*66*/(order);
}
break;
case OrderTypes.StopSell:
if (order.pos === PositionTypes.Long && stock.price <= order.price) {
executeOrder/*66*/(order);
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
executeOrder/*66*/(order);
}
break;
default:
console.log("Invalid order type: " + order.type);
return;
}
}
}
}
let stockMarketContainer = null;
function setStockMarketContainer() {
stockMarketContainer = document.getElementById("stock-market-container");
@ -599,7 +265,6 @@ function setStockMarketContainer() {
document.addEventListener("DOMContentLoaded", setStockMarketContainer);
function initStockMarketFnForReact() {
initStockMarket();
initSymbolToStockMap();

@ -3,7 +3,7 @@ import { PositionTypes } from "./data/PositionTypes";
import { CONSTANTS } from "../Constants";
// Amount by which a stock's forecast changes during each price movement
const forecastChangePerPriceMovement = 0.4;
export const forecastChangePerPriceMovement = 0.4;
/**
* Given a stock, calculates the amount by which the stock price is multiplied
@ -103,16 +103,6 @@ export function processBuyTransactionPriceMovement(stock: Stock, shares: number,
const isLong = (posType === PositionTypes.Long);
// If the number of shares doesn't trigger a price movement, just return
if (shares <= stock.shareTxUntilMovement) {
stock.shareTxUntilMovement -= shares;
return;
}
// Calculate how many iterations of price changes we need to account for
let remainingShares = shares - stock.shareTxUntilMovement;
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
let currPrice = stock.price;
function processPriceMovement() {
if (isLong) {
@ -122,15 +112,38 @@ export function processBuyTransactionPriceMovement(stock: Stock, shares: number,
}
}
// No price/forecast movement
if (shares <= stock.shareTxUntilMovement) {
stock.shareTxUntilMovement -= shares;
if (stock.shareTxUntilMovement <= 0) {
stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement();
stock.price = currPrice;
stock.otlkMag -= (forecastChangePerPriceMovement);
}
return;
}
// Calculate how many iterations of price changes we need to account for
let remainingShares = shares - stock.shareTxUntilMovement;
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
for (let i = 1; i < numIterations; ++i) {
processPriceMovement();
}
stock.price = currPrice;
stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
if (stock.shareTxUntilMovement === stock.shareTxForMovement || stock.shareTxUntilMovement <= 0) {
// The shareTxUntilMovement ended up at 0 at the end of the "processing"
++numIterations;
stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement();
}
stock.price = currPrice;
// Forecast always decreases in magnitude
const forecastChange = Math.min(5, forecastChangePerPriceMovement * numIterations);
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
stock.otlkMag -= forecastChange;
}
@ -214,18 +227,8 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
const isLong = (posType === PositionTypes.Long);
if (shares <= stock.shareTxUntilMovement) {
stock.shareTxUntilMovement -= shares;
return;
}
// Calculate how many iterations of price changes we need to accoutn for
let remainingShares = shares - stock.shareTxUntilMovement;
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
let currPrice = stock.price;
for (let i = 1; i < numIterations; ++i) {
// Price movement
function processPriceMovement() {
if (isLong) {
currPrice *= calculateDecreasingPriceMovement(stock)!;
} else {
@ -233,11 +236,37 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
}
}
// No price/forecast movement
if (shares <= stock.shareTxUntilMovement) {
stock.shareTxUntilMovement -= shares;
if (stock.shareTxUntilMovement <= 0) {
stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement();
stock.price = currPrice;
stock.otlkMag -= (forecastChangePerPriceMovement);
}
return;
}
// Calculate how many iterations of price changes we need to account for
let remainingShares = shares - stock.shareTxUntilMovement;
let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement);
for (let i = 1; i < numIterations; ++i) {
processPriceMovement();
}
stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
if (stock.shareTxUntilMovement === stock.shareTxForMovement || stock.shareTxUntilMovement <= 0) {
++numIterations;
stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement();
}
stock.price = currPrice;
// Forecast always decreases in magnitude
const forecastChange = Math.min(5, forecastChangePerPriceMovement * numIterations);
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
stock.otlkMag -= forecastChange;
}

@ -18,7 +18,6 @@ import {
import { OrderTypes } from "../data/OrderTypes";
import { PositionTypes } from "../data/PositionTypes";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { numeralWrapper } from "../../ui/numeralFormat";
@ -58,6 +57,7 @@ type IState = {
orderType: SelectorOrderType;
position: PositionTypes;
qty: string;
rerenderFlag: boolean;
}
export class StockTicker extends React.Component<IProps, IState> {
@ -68,6 +68,7 @@ export class StockTicker extends React.Component<IProps, IState> {
orderType: SelectorOrderType.Market,
position: PositionTypes.Long,
qty: "",
rerenderFlag: false,
}
this.getBuyTransactionCostText = this.getBuyTransactionCostText.bind(this);
@ -80,6 +81,7 @@ export class StockTicker extends React.Component<IProps, IState> {
this.handleQuantityChange = this.handleQuantityChange.bind(this);
this.handleSellButtonClick = this.handleSellButtonClick.bind(this);
this.handleSellAllButtonClick = this.handleSellAllButtonClick.bind(this);
this.rerender = this.rerender.bind(this);
}
createPlaceOrderPopupBox(yesTxt: string, popupTxt: string, yesBtnCb: (price: number) => void) {
@ -172,6 +174,7 @@ export class StockTicker extends React.Component<IProps, IState> {
} else {
this.props.buyStockLong(this.props.stock, shares);
}
this.rerender();
break;
}
case SelectorOrderType.Limit: {
@ -213,30 +216,13 @@ export class StockTicker extends React.Component<IProps, IState> {
} else {
this.props.buyStockLong(stock, maxShares);
}
this.rerender();
break;
}
case SelectorOrderType.Limit: {
this.createPlaceOrderPopupBox(
"Place Buy Limit Order",
"Enter the price for your Limit Order",
(price: number) => {
this.props.placeOrder(stock, maxShares, price, OrderTypes.LimitBuy, this.state.position);
}
);
default: {
dialogBoxCreate(`ERROR: 'Buy Max' only works for Market Orders`);
break;
}
case SelectorOrderType.Stop: {
this.createPlaceOrderPopupBox(
"Place Buy Stop Order",
"Enter the price for your Stop Order",
(price: number) => {
this.props.placeOrder(stock, maxShares, price, OrderTypes.StopBuy, this.state.position);
}
)
break;
}
default:
break;
}
}
@ -311,6 +297,7 @@ export class StockTicker extends React.Component<IProps, IState> {
} else {
this.props.sellStockLong(this.props.stock, shares);
}
this.rerender();
break;
}
case SelectorOrderType.Limit: {
@ -348,6 +335,7 @@ export class StockTicker extends React.Component<IProps, IState> {
} else {
this.props.sellStockLong(stock, stock.playerShares);
}
this.rerender();
break;
}
default: {
@ -367,6 +355,14 @@ export class StockTicker extends React.Component<IProps, IState> {
return (this.props.p.bitNodeN === 8 || (SourceFileFlags[8] >= 2));
}
rerender(): void {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
}
});
}
render() {
// Determine if the player's intended transaction will cause a price movement
let causesMovement: boolean = false;

@ -18,7 +18,6 @@ type IProps = {
export function StockTickerHeaderText(props: IProps): React.ReactElement {
const stock = props.stock;
const p = props.p;
const stockPriceFormat = numeralWrapper.formatMoney(stock.price);

@ -28,7 +28,7 @@ export class StockTickerOrder extends React.Component<IProps, any> {
const order = this.props.order;
const posTxt = order.pos === PositionTypes.Long ? "Long Position" : "Short Position";
const txt = `${order.type} - ${posTxt} - ${order.shares} @ ${numeralWrapper.formatMoney(order.price)}`
const txt = `${order.type} - ${posTxt} - ${numeralWrapper.formatBigNumber(order.shares)} @ ${numeralWrapper.formatMoney(order.price)}`
return (
<li>

@ -57,6 +57,7 @@ import { killWorkerScript, addWorkerScript } from "./NetscriptWorker";
import { Player } from "./Player";
import { hackWorldDaemon } from "./RedPill";
import { RunningScript } from "./Script/RunningScript";
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
import { findRunningScript } from "./Script/ScriptHelpers";
import { isScriptFilename } from "./Script/ScriptHelpersTS";
import { AllServers } from "./Server/AllServers";
@ -1447,7 +1448,7 @@ let Terminal = {
let spacesThread = Array(numSpacesThread+1).join(" ");
// Calculate and transform RAM usage
let ramUsage = numeralWrapper.format(script.getRamUsage() * script.threads, '0.00') + " GB";
let ramUsage = numeralWrapper.format(getRamUsageFromRunningScript(script) * script.threads, '0.00') + " GB";
var entry = [script.filename, spacesScript, script.threads, spacesThread, ramUsage];
post(entry.join(""));
@ -2268,7 +2269,7 @@ let Terminal = {
// This has to come after addWorkerScript() because that fn
// updates the RAM usage. This kinda sucks, address if possible
server.runScript(runningScriptObj, Player);
server.runScript(runningScriptObj, Player.hacknet_node_money_mult);
return;
}
}

@ -49,11 +49,10 @@ import { LocationRoot } from "./Locations/ui/Root";
import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers";
import { inMission, currMission } from "./Missions";
import {
initSingularitySFFlags,
hasSingularitySF,
hasCorporationSF
} from "./NetscriptFunctions";
import { updateOnlineScriptTimes, runScriptsLoop } from "./NetscriptWorker";
loadAllRunningScripts,
runScriptsLoop,
updateOnlineScriptTimes,
} from "./NetscriptWorker";
import { Player } from "./Player";
import { prestigeAugmentation, prestigeSourceFile } from "./Prestige";
import { Programs } from "./Programs/Programs";
@ -66,7 +65,6 @@ import { redPillFlag, hackWorldDaemon } from "./RedPill";
import { saveObject, loadGame } from "./SaveObject";
import {
getCurrentEditor,
loadAllRunningScripts,
scriptEditorInit,
updateScriptEditorContent
} from "./Script/ScriptHelpers";
@ -1087,7 +1085,6 @@ const Engine = {
initSymbolToStockMap();
}
initLiterature();
initSingularitySFFlags();
updateSourceFileFlags(Player);
// Calculate the number of cycles have elapsed while offline
@ -1213,7 +1210,6 @@ const Engine = {
initAugmentations();
initMessages();
initLiterature();
initSingularitySFFlags();
// Open main menu accordions for new game
const hackingHdr = document.getElementById("hacking-menu-header");

@ -1,14 +1,28 @@
import { CONSTANTS } from "../src/Constants";
import { Stock } from "../src/StockMarket/Stock";
import { Order } from "../src/StockMarket/Order";
//import { processOrders } from "../src/StockMarket/OrderProcessing";
// import { Stock } from "../src/StockMarket/Stock";
import {
deleteStockMarket,
initStockMarket,
initSymbolToStockMap,
loadStockMarket,
StockMarket,
SymbolToStockMap,
} from "../src/StockMarket/StockMarket";
/*
import {
calculateIncreasingPriceMovement,
calculateDecreasingPriceMovement,
forecastChangePerPriceMovement,
getBuyTransactionCost,
getSellTransactionGain,
processBuyTransactionPriceMovement,
processSellTransactionPriceMovement,
} from "../src/StockMarket/StockMarketHelpers";
import { PositionTypes } from "../src/StockMarket/data/PositionTypes";
*/
// import { OrderTypes } from "../src/StockMarket/data/OrderTypes"
// import { PositionTypes } from "../src/StockMarket/data/PositionTypes";
const assert = chai.assert;
const expect = chai.expect;
@ -144,6 +158,67 @@ describe("Stock Market Tests", function() {
});
});
describe("StockMarket object", function() {
describe("Initialization", function() {
// Keeps track of initialized stocks. Contains their symbols
const stocks = [];
before(function() {
expect(initStockMarket).to.not.throw();
expect(initSymbolToStockMap).to.not.throw();
});
it("should have Stock objects", function() {
for (const prop in StockMarket) {
const stock = StockMarket[prop];
if (stock instanceof Stock) {
stocks.push(stock.symbol);
}
}
// We'll just check that there are some stocks
expect(stocks.length).to.be.at.least(1);
});
it("should have an order book in the 'Orders' property", function() {
expect(StockMarket).to.have.property("Orders");
const orderbook = StockMarket["Orders"];
for (const symbol of stocks) {
const ordersForStock = orderbook[symbol];
expect(ordersForStock).to.be.an("array");
expect(ordersForStock.length).to.equal(0);
}
});
it("should have properties for managing game cycles", function() {
expect(StockMarket).to.have.property("storedCycles");
expect(StockMarket).to.have.property("lastUpdate");
});
});
// Because 'StockMarket' is a global object, the effects of initialization from
// the block above should still stand
describe("Deletion", function() {
it("should set StockMarket to be an empty object", function() {
expect(StockMarket).to.be.an("object").that.is.not.empty;
deleteStockMarket();
expect(StockMarket).to.be.an("object").that.is.empty;
});
});
// Reset stock market for each test
beforeEach(function() {
deleteStockMarket();
initStockMarket();
initSymbolToStockMap();
});
it("should properly initialize", function() {
});
});
describe("Transaction Cost Calculator Functions", function() {
describe("getBuyTransactionCost()", function() {
it("should fail on invalid 'stock' argument", function() {
@ -279,6 +354,31 @@ describe("Stock Market Tests", function() {
});
describe("Price Movement Processor Functions", function() {
// N = 1 is the original price
function getNthPriceIncreasing(origPrice, n) {
let price = origPrice;
for (let i = 1; i < n; ++i) {
price *= calculateIncreasingPriceMovement(stock);
}
return price;
}
// N = 1 is the original price
function getNthPriceDecreasing(origPrice, n) {
let price = origPrice;
for (let i = 1; i < n; ++i) {
price *= calculateDecreasingPriceMovement(stock);
}
return price;
}
// N = 1 is the original forecast
function getNthForecast(origForecast, n) {
return origForecast - forecastChangePerPriceMovement * (n - 1);
}
describe("processBuyTransactionPriceMovement()", function() {
const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2);
const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares;
@ -305,53 +405,107 @@ describe("Stock Market Tests", function() {
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
});
it("should properly evaluate LONG transactions that doesn't trigger a price movement", function() {
it("should properly evaluate a LONG transaction that doesn't trigger a price movement", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
expect(stock.price).to.equal(oldPrice);
expect(stock.otlkMag).to.equal(oldForecast);
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate SHORT transactions that doesn't trigger a price movement", function() {
it("should properly evaluate a SHORT transaction that doesn't trigger a price movement", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
expect(stock.price).to.equal(oldPrice);
expect(stock.otlkMag).to.equal(oldForecast);
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate LONG transactions that trigger price movements", function() {
const oldPrice = stock.price;
function getNthPrice(n) {
let price = oldPrice;
for (let i = 1; i < n; ++i) {
price *= calculateIncreasingPriceMovement(stock);
}
return price;
}
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, mvmtShares, PositionTypes.Long);
expect(stock.price).to.equal(getNthPrice(4));
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate SHORT transactions that trigger price movements", function() {
const oldPrice = stock.price;
function getNthPrice(n) {
let price = oldPrice;
for (let i = 1; i < n; ++i) {
price *= calculateDecreasingPriceMovement(stock);
}
return price;
}
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, mvmtShares, PositionTypes.Short);
expect(stock.price).to.equal(getNthPrice(4));
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long);
processBuyTransactionPriceMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short);
processBuyTransactionPriceMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processBuyTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
});
describe("processSellTransactionPriceMovement()", function() {
@ -380,53 +534,137 @@ describe("Stock Market Tests", function() {
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
});
it("should properly evaluate LONG transactions that doesn't trigger a price movement", function() {
it("should properly evaluate a LONG transaction that doesn't trigger a price movement", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
expect(stock.price).to.equal(oldPrice);
expect(stock.otlkMag).to.equal(oldForecast);
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate SHORT transactions that doesn't trigger a price movement", function() {
it("should properly evaluate a SHORT transaction that doesn't trigger a price movement", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
expect(stock.price).to.equal(oldPrice);
expect(stock.otlkMag).to.equal(oldForecast);
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate LONG transactions that trigger price movements", function() {
const oldPrice = stock.price;
function getNthPrice(n) {
let price = oldPrice;
for (let i = 1; i < n; ++i) {
price *= calculateDecreasingPriceMovement(stock);
}
return price;
}
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, mvmtShares, PositionTypes.Long);
expect(stock.price).to.equal(getNthPrice(4));
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate SHORT transactions that trigger price movements", function() {
const oldPrice = stock.price;
function getNthPrice(n) {
let price = oldPrice;
for (let i = 1; i < n; ++i) {
price *= calculateIncreasingPriceMovement(stock);
}
return price;
}
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, mvmtShares, PositionTypes.Short);
expect(stock.price).to.equal(getNthPrice(4));
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
});
it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long);
processSellTransactionPriceMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long);
expect(stock.price).to.equal(getNthPriceDecreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, stock.shareTxForMovement, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short);
processSellTransactionPriceMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 2));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() {
const oldPrice = stock.price;
const oldForecast = stock.otlkMag;
processSellTransactionPriceMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short);
expect(stock.price).to.equal(getNthPriceIncreasing(oldPrice, 4));
expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4));
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement);
});
});
});
describe("Order Class", function() {
it("should throw on invalid arguments", function() {
function invalid1() {
return new Order({}, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long);
}
function invalid2() {
return new Order(new Stock(), "z", 0, OrderTypes.LimitBuy, PositionTypes.Short);
}
function invalid3() {
return new Order(new Stock(), 1, {}, OrderTypes.LimitBuy, PositionTypes.Short);
}
function invalid4() {
return new Order(new Stock(), 1, NaN, OrderTypes.LimitBuy, PositionTypes.Short);
}
function invalid5() {
return new Order(new Stock(), NaN, 0, OrderTypes.LimitBuy, PositionTypes.Short);
}
expect(invalid1).to.throw();
expect(invalid2).to.throw();
expect(invalid3).to.throw();
expect(invalid4).to.throw();
expect(invalid5).to.throw();
});
});
describe("Order Processing", function() {
});
});

@ -1,25 +1,14 @@
import { AllServers } from "../src/Server/AllServers";
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
/**
* Generate a random IP address
* Does not check to see if the IP already exists in the game
*/
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);
}