mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-24 07:02:26 +01:00
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:
parent
585e1ac7aa
commit
8a5b6f6cbc
@ -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
|
This is an important concept to keep in mind if you are trying to purchase/sell a
|
||||||
large number of shares, as **it can negatively affect your profits**.
|
large number of shares, as **it can negatively affect your profits**.
|
||||||
|
|
||||||
|
.. note:: On the Stock Market UI, the displayed profit of a position does not take
|
||||||
|
this "influencing" into account.
|
||||||
|
|
||||||
Transactions Influencing Stock Forecast
|
Transactions Influencing Stock Forecast
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
In addition to influencing stock price, buying or selling a large number of shares
|
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.
|
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
|
Automating the Stock Market
|
||||||
---------------------------
|
---------------------------
|
||||||
You can write scripts to perform automatic and algorithmic trading on the Stock Market.
|
You can write scripts to perform automatic and algorithmic trading on the Stock Market.
|
||||||
See :ref:`netscript_tixapi` for more details.
|
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 { CONSTANTS } from "../Constants";
|
||||||
import { Factions,
|
import { Factions,
|
||||||
factionExists } from "../Faction/Factions";
|
factionExists } from "../Faction/Factions";
|
||||||
import { hasBladeburnerSF } from "../NetscriptFunctions";
|
|
||||||
import { addWorkerScript } from "../NetscriptWorker";
|
import { addWorkerScript } from "../NetscriptWorker";
|
||||||
import { Player } from "../Player";
|
import { Player } from "../Player";
|
||||||
import { prestigeAugmentation } from "../Prestige";
|
import { prestigeAugmentation } from "../Prestige";
|
||||||
@ -2094,7 +2093,7 @@ function installAugmentations(cbScript=null) {
|
|||||||
}
|
}
|
||||||
var runningScriptObj = new RunningScript(script, []); //No args
|
var runningScriptObj = new RunningScript(script, []); //No args
|
||||||
runningScriptObj.threads = 1; //Only 1 thread
|
runningScriptObj.threads = 1; //Only 1 thread
|
||||||
home.runScript(runningScriptObj, Player);
|
home.runScript(runningScriptObj, Player.hacknet_node_money_mult);
|
||||||
addWorkerScript(runningScriptObj, home);
|
addWorkerScript(runningScriptObj, home);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,60 +38,6 @@ export let CONSTANTS: IMap<any> = {
|
|||||||
// NeuroFlux Governor Augmentation cost multiplier
|
// NeuroFlux Governor Augmentation cost multiplier
|
||||||
NeuroFluxGovernorLevelMult: 1.14,
|
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,
|
NumNetscriptPorts: 20,
|
||||||
|
|
||||||
// Server-related constants
|
// Server-related constants
|
||||||
@ -287,6 +233,8 @@ export let CONSTANTS: IMap<any> = {
|
|||||||
* Re-sleeves can no longer have the NeuroFlux Governor augmentation
|
* Re-sleeves can no longer have the NeuroFlux Governor augmentation
|
||||||
** This is just a temporary patch until the mechanic gets re-worked
|
** This is just a temporary patch until the mechanic gets re-worked
|
||||||
|
|
||||||
|
* 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
|
* Corporation employees no longer have an "age" stat
|
||||||
* Bug Fix: Corporation employees stats should no longer become negative
|
* Bug Fix: Corporation employees stats should no longer become negative
|
||||||
* Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios
|
* Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios
|
||||||
|
@ -6,34 +6,35 @@
|
|||||||
*/
|
*/
|
||||||
import { IReturnStatus } from "../types";
|
import { IReturnStatus } from "../types";
|
||||||
|
|
||||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
//import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
import { Server } from "../Server/Server";
|
import { Server } from "../Server/Server";
|
||||||
|
|
||||||
function baseCheck(server: Server | HacknetServer, fnName: string): IReturnStatus {
|
function baseCheck(server: Server, fnName: string): IReturnStatus {
|
||||||
if (server instanceof HacknetServer) {
|
const hostname = server.hostname;
|
||||||
|
|
||||||
|
if (!("requiredHackingSkill" in server)) {
|
||||||
return {
|
return {
|
||||||
res: false,
|
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) {
|
if (server.hasAdminRights === false) {
|
||||||
return {
|
return {
|
||||||
res: false,
|
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 }
|
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");
|
const initialCheck = baseCheck(server, "hack");
|
||||||
if (!initialCheck.res) { return initialCheck; }
|
if (!initialCheck.res) { return initialCheck; }
|
||||||
|
|
||||||
let s = <Server>server;
|
let s = server;
|
||||||
|
|
||||||
if (s.requiredHackingSkill > p.hacking_skill) {
|
if (s.requiredHackingSkill > p.hacking_skill) {
|
||||||
return {
|
return {
|
||||||
res: false,
|
res: false,
|
||||||
@ -44,10 +45,10 @@ export function netscriptCanHack(server: Server | HacknetServer, p: IPlayer): IR
|
|||||||
return { res: true }
|
return { res: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function netscriptCanGrow(server: Server | HacknetServer): IReturnStatus {
|
export function netscriptCanGrow(server: Server): IReturnStatus {
|
||||||
return baseCheck(server, "grow");
|
return baseCheck(server, "grow");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function netscriptCanWeaken(server: Server | HacknetServer): IReturnStatus {
|
export function netscriptCanWeaken(server: Server): IReturnStatus {
|
||||||
return baseCheck(server, "weaken");
|
return baseCheck(server, "weaken");
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,41 @@
|
|||||||
import { HacknetNode,
|
import {
|
||||||
BaseCostForHacknetNode,
|
HacknetNode,
|
||||||
HacknetNodePurchaseNextMult,
|
BaseCostForHacknetNode,
|
||||||
HacknetNodeMaxLevel,
|
HacknetNodePurchaseNextMult,
|
||||||
HacknetNodeMaxRam,
|
HacknetNodeMaxLevel,
|
||||||
HacknetNodeMaxCores } from "./HacknetNode";
|
HacknetNodeMaxRam,
|
||||||
import { HacknetServer,
|
HacknetNodeMaxCores
|
||||||
BaseCostForHacknetServer,
|
} from "./HacknetNode";
|
||||||
HacknetServerPurchaseMult,
|
import {
|
||||||
HacknetServerMaxLevel,
|
HacknetServer,
|
||||||
HacknetServerMaxRam,
|
BaseCostForHacknetServer,
|
||||||
HacknetServerMaxCores,
|
HacknetServerPurchaseMult,
|
||||||
HacknetServerMaxCache,
|
HacknetServerMaxLevel,
|
||||||
MaxNumberHacknetServers } from "./HacknetServer";
|
HacknetServerMaxRam,
|
||||||
import { HashManager } from "./HashManager";
|
HacknetServerMaxCores,
|
||||||
import { HashUpgrades } from "./HashUpgrades";
|
HacknetServerMaxCache,
|
||||||
|
MaxNumberHacknetServers
|
||||||
|
} from "./HacknetServer";
|
||||||
|
import { HashManager } from "./HashManager";
|
||||||
|
import { HashUpgrades } from "./HashUpgrades";
|
||||||
|
|
||||||
import { generateRandomContract } from "../CodingContractGenerator";
|
import { generateRandomContract } from "../CodingContractGenerator";
|
||||||
import { iTutorialSteps, iTutorialNextStep,
|
import {
|
||||||
ITutorial} from "../InteractiveTutorial";
|
iTutorialSteps,
|
||||||
import { Player } from "../Player";
|
iTutorialNextStep,
|
||||||
import { AddToAllServers,
|
ITutorial
|
||||||
AllServers } from "../Server/AllServers";
|
} from "../InteractiveTutorial";
|
||||||
import { GetServerByHostname } from "../Server/ServerHelpers";
|
import { Player } from "../Player";
|
||||||
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
import { AddToAllServers, AllServers } from "../Server/AllServers";
|
||||||
import { Page, routing } from "../ui/navigationTracking";
|
import { GetServerByHostname } from "../Server/ServerHelpers";
|
||||||
|
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
||||||
|
import { Page, routing } from "../ui/navigationTracking";
|
||||||
|
|
||||||
import {getElementById} from "../../utils/uiHelpers/getElementById";
|
import { getElementById } from "../../utils/uiHelpers/getElementById";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { HacknetRoot } from "./ui/Root";
|
import { HacknetRoot } from "./ui/Root";
|
||||||
|
|
||||||
let hacknetNodesDiv;
|
let hacknetNodesDiv;
|
||||||
function hacknetNodesInit() {
|
function hacknetNodesInit() {
|
||||||
@ -66,7 +72,7 @@ export function purchaseHacknet() {
|
|||||||
if (!Player.canAfford(cost)) { return -1; }
|
if (!Player.canAfford(cost)) { return -1; }
|
||||||
Player.loseMoney(cost);
|
Player.loseMoney(cost);
|
||||||
const server = Player.createHacknetServer();
|
const server = Player.createHacknetServer();
|
||||||
Player.hashManager.updateCapacity(Player);
|
Player.hashManager.updateCapacity(Player.hacknetNodes);
|
||||||
|
|
||||||
return numOwned;
|
return numOwned;
|
||||||
} else {
|
} else {
|
||||||
@ -79,8 +85,7 @@ export function purchaseHacknet() {
|
|||||||
|
|
||||||
// Auto generate a name for the Node
|
// Auto generate a name for the Node
|
||||||
const name = "hacknet-node-" + numOwned;
|
const name = "hacknet-node-" + numOwned;
|
||||||
const node = new HacknetNode(name);
|
const node = new HacknetNode(name, Player.hacknet_node_money_mult);
|
||||||
node.updateMoneyGainRate(Player);
|
|
||||||
|
|
||||||
Player.loseMoney(cost);
|
Player.loseMoney(cost);
|
||||||
Player.hacknetNodes.push(node);
|
Player.hacknetNodes.push(node);
|
||||||
@ -116,26 +121,26 @@ export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) {
|
|||||||
throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let min = 1;
|
let min = 1;
|
||||||
let max = maxLevel - 1;
|
let max = maxLevel - 1;
|
||||||
let levelsToMax = maxLevel - nodeObj.level;
|
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;
|
return levelsToMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (min <= max) {
|
while (min <= max) {
|
||||||
var curr = (min + max) / 2 | 0;
|
var curr = (min + max) / 2 | 0;
|
||||||
if (curr !== maxLevel &&
|
if (curr !== maxLevel &&
|
||||||
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player)) &&
|
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult)) &&
|
||||||
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player))) {
|
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player.hacknet_node_level_cost_mult))) {
|
||||||
return Math.min(levelsToMax, curr);
|
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;
|
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;
|
min = curr + 1;
|
||||||
} else {
|
} else {
|
||||||
return Math.min(levelsToMax, curr);
|
return Math.min(levelsToMax, curr);
|
||||||
@ -149,7 +154,7 @@ export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
|
|||||||
throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,13 +164,13 @@ export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
|
|||||||
} else {
|
} else {
|
||||||
levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram));
|
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;
|
return levelsToMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
//We'll just loop until we find the max
|
//We'll just loop until we find the max
|
||||||
for (let i = levelsToMax-1; i >= 0; --i) {
|
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;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,14 +182,14 @@ export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
|
|||||||
throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let min = 1;
|
let min = 1;
|
||||||
let max = maxLevel - 1;
|
let max = maxLevel - 1;
|
||||||
const levelsToMax = maxLevel - nodeObj.cores;
|
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;
|
return levelsToMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,12 +197,12 @@ export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
|
|||||||
while (min <= max) {
|
while (min <= max) {
|
||||||
let curr = (min + max) / 2 | 0;
|
let curr = (min + max) / 2 | 0;
|
||||||
if (curr != maxLevel &&
|
if (curr != maxLevel &&
|
||||||
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player)) &&
|
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult)) &&
|
||||||
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player))) {
|
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player.hacknet_node_core_cost_mult))) {
|
||||||
return Math.min(levelsToMax, curr);
|
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;
|
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;
|
min = curr + 1;
|
||||||
} else {
|
} else {
|
||||||
return Math.min(levelsToMax, curr);
|
return Math.min(levelsToMax, curr);
|
||||||
@ -242,7 +247,134 @@ export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) {
|
|||||||
return 0;
|
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() {
|
export function renderHacknetNodesUI() {
|
||||||
if (!routing.isOn(Page.HacknetNodes)) { return; }
|
if (!routing.isOn(Page.HacknetNodes)) { return; }
|
||||||
|
|
||||||
@ -303,6 +435,37 @@ function processAllHacknetServerEarnings(numCycles) {
|
|||||||
return hashes;
|
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) {
|
export function purchaseHashUpgrade(upgName, upgTarget) {
|
||||||
if (!(Player.hashManager instanceof HashManager)) {
|
if (!(Player.hashManager instanceof HashManager)) {
|
||||||
console.error(`Player does not have a HashManager`);
|
console.error(`Player does not have a HashManager`);
|
||||||
|
@ -62,12 +62,14 @@ export class HacknetNode implements IHacknetNode {
|
|||||||
// Total money earned by this Node
|
// Total money earned by this Node
|
||||||
totalMoneyGenerated: number = 0;
|
totalMoneyGenerated: number = 0;
|
||||||
|
|
||||||
constructor(name: string="") {
|
constructor(name: string="", prodMult: number=1) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
|
this.updateMoneyGainRate(prodMult);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the cost to upgrade this Node's number of cores
|
// 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);
|
const sanitizedLevels = Math.round(levels);
|
||||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -86,13 +88,13 @@ export class HacknetNode implements IHacknetNode {
|
|||||||
++currentCores;
|
++currentCores;
|
||||||
}
|
}
|
||||||
|
|
||||||
totalCost *= p.hacknet_node_core_cost_mult;
|
totalCost *= costMult;
|
||||||
|
|
||||||
return totalCost;
|
return totalCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the cost to upgrade this Node's level
|
// 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);
|
const sanitizedLevels = Math.round(levels);
|
||||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -110,11 +112,11 @@ export class HacknetNode implements IHacknetNode {
|
|||||||
++currLevel;
|
++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
|
// 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);
|
const sanitizedLevels = Math.round(levels);
|
||||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -138,7 +140,7 @@ export class HacknetNode implements IHacknetNode {
|
|||||||
++numUpgrades;
|
++numUpgrades;
|
||||||
}
|
}
|
||||||
|
|
||||||
totalCost *= p.hacknet_node_ram_cost_mult;
|
totalCost *= costMult;
|
||||||
|
|
||||||
return totalCost;
|
return totalCost;
|
||||||
}
|
}
|
||||||
@ -161,112 +163,37 @@ export class HacknetNode implements IHacknetNode {
|
|||||||
|
|
||||||
// Upgrade this Node's number of cores, if possible
|
// Upgrade this Node's number of cores, if possible
|
||||||
// Returns a boolean indicating whether new cores were successfully bought
|
// Returns a boolean indicating whether new cores were successfully bought
|
||||||
purchaseCoreUpgrade(levels: number=1, p: IPlayer): boolean {
|
upgradeCore(levels: number=1, prodMult: number): void {
|
||||||
const sanitizedLevels = Math.round(levels);
|
this.cores = Math.min(HacknetNodeMaxCores, Math.round(this.cores + levels));
|
||||||
const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
|
this.updateMoneyGainRate(prodMult);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade this Node's level, if possible
|
// Upgrade this Node's level, if possible
|
||||||
// Returns a boolean indicating whether the level was successfully updated
|
// Returns a boolean indicating whether the level was successfully updated
|
||||||
purchaseLevelUpgrade(levels: number=1, p: IPlayer): boolean {
|
upgradeLevel(levels: number=1, prodMult: number): void {
|
||||||
const sanitizedLevels = Math.round(levels);
|
this.level = Math.min(HacknetNodeMaxLevel, Math.round(this.level + levels));
|
||||||
const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
|
this.updateMoneyGainRate(prodMult);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade this Node's RAM, if possible
|
// Upgrade this Node's RAM, if possible
|
||||||
// Returns a boolean indicating whether the RAM was successfully upgraded
|
// Returns a boolean indicating whether the RAM was successfully upgraded
|
||||||
purchaseRamUpgrade(levels: number=1, p: IPlayer): boolean {
|
upgradeRam(levels: number=1, prodMult: number): void {
|
||||||
const sanitizedLevels = Math.round(levels);
|
for (let i = 0; i < levels; ++i) {
|
||||||
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) {
|
|
||||||
this.ram *= 2; // Ram is always doubled
|
this.ram *= 2; // Ram is always doubled
|
||||||
}
|
}
|
||||||
this.ram = Math.round(this.ram); // Handle any floating point precision issues
|
this.ram = Math.round(this.ram); // Handle any floating point precision issues
|
||||||
this.updateMoneyGainRate(p);
|
this.updateMoneyGainRate(prodMult);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-calculate this Node's production and update the moneyGainRatePerSecond prop
|
// 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
|
//How much extra $/s is gained per level
|
||||||
var gainPerLevel = HacknetNodeMoneyGainPerLevel;
|
var gainPerLevel = HacknetNodeMoneyGainPerLevel;
|
||||||
|
|
||||||
this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
|
this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
|
||||||
Math.pow(1.035, this.ram - 1) *
|
Math.pow(1.035, this.ram - 1) *
|
||||||
((this.cores + 5) / 6) *
|
((this.cores + 5) / 6) *
|
||||||
p.hacknet_node_money_mult *
|
prodMult *
|
||||||
BitNodeMultipliers.HacknetNodeMoney;
|
BitNodeMultipliers.HacknetNodeMoney;
|
||||||
if (isNaN(this.moneyGainRatePerSecond)) {
|
if (isNaN(this.moneyGainRatePerSecond)) {
|
||||||
this.moneyGainRatePerSecond = 0;
|
this.moneyGainRatePerSecond = 0;
|
||||||
|
@ -6,15 +6,16 @@ import { CONSTANTS } from "../Constants";
|
|||||||
import { IHacknetNode } from "./IHacknetNode";
|
import { IHacknetNode } from "./IHacknetNode";
|
||||||
|
|
||||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
|
||||||
import { BaseServer } from "../Server/BaseServer";
|
import { BaseServer } from "../Server/BaseServer";
|
||||||
import { RunningScript } from "../Script/RunningScript";
|
import { RunningScript } from "../Script/RunningScript";
|
||||||
|
|
||||||
import { createRandomIp } from "../../utils/IPAddress";
|
import { createRandomIp } from "../../utils/IPAddress";
|
||||||
|
|
||||||
import { Generic_fromJSON,
|
import {
|
||||||
Generic_toJSON,
|
Generic_fromJSON,
|
||||||
Reviver } from "../../utils/JSONReviver";
|
Generic_toJSON,
|
||||||
|
Reviver
|
||||||
|
} from "../../utils/JSONReviver";
|
||||||
|
|
||||||
// Constants for Hacknet Server stats/production
|
// Constants for Hacknet Server stats/production
|
||||||
export const HacknetServerHashesPerLevel: number = 0.001;
|
export const HacknetServerHashesPerLevel: number = 0.001;
|
||||||
@ -46,7 +47,6 @@ interface IConstructorParams {
|
|||||||
isConnectedTo?: boolean;
|
isConnectedTo?: boolean;
|
||||||
maxRam?: number;
|
maxRam?: number;
|
||||||
organizationName?: string;
|
organizationName?: string;
|
||||||
player?: IPlayer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HacknetServer extends BaseServer implements IHacknetNode {
|
export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||||
@ -81,10 +81,6 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
|||||||
|
|
||||||
this.maxRam = 1;
|
this.maxRam = 1;
|
||||||
this.updateHashCapacity();
|
this.updateHashCapacity();
|
||||||
|
|
||||||
if (params.player) {
|
|
||||||
this.updateHashRate(params.player);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateCacheUpgradeCost(levels: number): number {
|
calculateCacheUpgradeCost(levels: number): number {
|
||||||
@ -109,7 +105,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
|||||||
return totalCost;
|
return totalCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateCoreUpgradeCost(levels: number, p: IPlayer): number {
|
calculateCoreUpgradeCost(levels: number, costMult: number): number {
|
||||||
const sanitizedLevels = Math.round(levels);
|
const sanitizedLevels = Math.round(levels);
|
||||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -127,12 +123,12 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
|||||||
++currentCores;
|
++currentCores;
|
||||||
}
|
}
|
||||||
totalCost *= BaseCostForHacknetServerCore;
|
totalCost *= BaseCostForHacknetServerCore;
|
||||||
totalCost *= p.hacknet_node_core_cost_mult;
|
totalCost *= costMult;
|
||||||
|
|
||||||
return totalCost;
|
return totalCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateLevelUpgradeCost(levels: number, p: IPlayer): number {
|
calculateLevelUpgradeCost(levels: number, costMult: number): number {
|
||||||
const sanitizedLevels = Math.round(levels);
|
const sanitizedLevels = Math.round(levels);
|
||||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -150,10 +146,10 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
|||||||
++currLevel;
|
++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);
|
const sanitizedLevels = Math.round(levels);
|
||||||
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -175,7 +171,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
|||||||
currentRam *= 2;
|
currentRam *= 2;
|
||||||
++numUpgrades;
|
++numUpgrades;
|
||||||
}
|
}
|
||||||
totalCost *= p.hacknet_node_ram_cost_mult;
|
totalCost *= costMult;
|
||||||
|
|
||||||
return totalCost;
|
return totalCost;
|
||||||
}
|
}
|
||||||
@ -189,124 +185,30 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns a boolean indicating whether the cache was successfully upgraded
|
// Returns a boolean indicating whether the cache was successfully upgraded
|
||||||
purchaseCacheUpgrade(levels: number, p: IPlayer): boolean {
|
upgradeCache(levels: number): void {
|
||||||
const sanitizedLevels = Math.round(levels);
|
this.cache = Math.min(HacknetServerMaxCache, Math.round(this.cache + 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);
|
|
||||||
this.updateHashCapacity();
|
this.updateHashCapacity();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a boolean indicating whether the number of cores was successfully upgraded
|
// Returns a boolean indicating whether the number of cores was successfully upgraded
|
||||||
purchaseCoreUpgrade(levels: number, p: IPlayer): boolean {
|
upgradeCore(levels: number, prodMult: number): void {
|
||||||
const sanitizedLevels = Math.round(levels);
|
this.cores = Math.min(HacknetServerMaxCores, Math.round(this.cores + levels));
|
||||||
const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
|
this.updateHashRate(prodMult);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a boolean indicating whether the level was successfully upgraded
|
// Returns a boolean indicating whether the level was successfully upgraded
|
||||||
purchaseLevelUpgrade(levels: number, p: IPlayer): boolean {
|
upgradeLevel(levels: number, prodMult: number): void {
|
||||||
const sanitizedLevels = Math.round(levels);
|
this.level = Math.min(HacknetServerMaxLevel, Math.round(this.level + levels));
|
||||||
const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
|
this.updateHashRate(prodMult);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a boolean indicating whether the RAM was successfully upgraded
|
// Returns a boolean indicating whether the RAM was successfully upgraded
|
||||||
purchaseRamUpgrade(levels: number, p: IPlayer): boolean {
|
upgradeRam(levels: number, prodMult: number): boolean {
|
||||||
const sanitizedLevels = Math.round(levels);
|
for (let i = 0; i < levels; ++i) {
|
||||||
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) {
|
|
||||||
this.maxRam *= 2;
|
this.maxRam *= 2;
|
||||||
}
|
}
|
||||||
this.maxRam = Math.round(this.maxRam);
|
this.maxRam = Math.min(HacknetServerMaxRam, Math.round(this.maxRam));
|
||||||
this.updateHashRate(p);
|
this.updateHashRate(prodMult);
|
||||||
|
|
||||||
return true;
|
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
|
* 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);
|
super.runScript(script);
|
||||||
if (p) {
|
if (prodMult != null && typeof prodMult === "number") {
|
||||||
this.updateHashRate(p);
|
this.updateHashRate(prodMult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,7 +227,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
|||||||
this.hashCapacity = 32 * Math.pow(2, this.cache);
|
this.hashCapacity = 32 * Math.pow(2, this.cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHashRate(p: IPlayer): void {
|
updateHashRate(prodMult: number): void {
|
||||||
const baseGain = HacknetServerHashesPerLevel * this.level;
|
const baseGain = HacknetServerHashesPerLevel * this.level;
|
||||||
const ramMultiplier = Math.pow(1.07, Math.log2(this.maxRam));
|
const ramMultiplier = Math.pow(1.07, Math.log2(this.maxRam));
|
||||||
const coreMultiplier = 1 + (this.cores - 1) / 5;
|
const coreMultiplier = 1 + (this.cores - 1) / 5;
|
||||||
@ -333,7 +235,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
|
|||||||
|
|
||||||
const hashRate = baseGain * ramMultiplier * coreMultiplier * ramRatio;
|
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)) {
|
if (isNaN(this.hashRate)) {
|
||||||
this.hashRate = 0;
|
this.hashRate = 0;
|
||||||
|
@ -6,12 +6,9 @@
|
|||||||
* his hashes, and contains method for grabbing the data/multipliers from
|
* his hashes, and contains method for grabbing the data/multipliers from
|
||||||
* those upgrades
|
* those upgrades
|
||||||
*/
|
*/
|
||||||
import { HacknetServer } from "./HacknetServer";
|
|
||||||
import { HashUpgrades } from "./HashUpgrades";
|
import { HashUpgrades } from "./HashUpgrades";
|
||||||
|
|
||||||
import { IMap } from "../types";
|
import { IMap } from "../types";
|
||||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
|
||||||
import { AllServers } from "../Server/AllServers";
|
|
||||||
import { Generic_fromJSON,
|
import { Generic_fromJSON,
|
||||||
Generic_toJSON,
|
Generic_toJSON,
|
||||||
Reviver } from "../../utils/JSONReviver";
|
Reviver } from "../../utils/JSONReviver";
|
||||||
@ -84,14 +81,14 @@ export class HashManager {
|
|||||||
return upg.getCost(currLevel);
|
return upg.getCost(currLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
prestige(p: IPlayer): void {
|
prestige(): void {
|
||||||
for (const name in HashUpgrades) {
|
for (const name in HashUpgrades) {
|
||||||
this.upgrades[name] = 0;
|
this.upgrades[name] = 0;
|
||||||
}
|
}
|
||||||
this.hashes = 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);
|
this.hashes = Math.min(this.hashes, this.capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCapacity(p: IPlayer): void {
|
updateCapacity(newCap: number): void {
|
||||||
if (p.hacknetNodes.length <= 0) {
|
if (newCap < 0) {
|
||||||
this.capacity = 0;
|
this.capacity = 0;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
this.capacity = Math.max(newCap, 0);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
// Interface for a Hacknet Node. Implemented by both a basic Hacknet Node,
|
// Interface for a Hacknet Node. Implemented by both a basic Hacknet Node,
|
||||||
// and the upgraded Hacknet Server in BitNode-9
|
// and the upgraded Hacknet Server in BitNode-9
|
||||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
|
||||||
|
|
||||||
export interface IHacknetNode {
|
export interface IHacknetNode {
|
||||||
cores: number;
|
cores: number;
|
||||||
level: number;
|
level: number;
|
||||||
onlineTimeSeconds: number;
|
onlineTimeSeconds: number;
|
||||||
|
|
||||||
calculateCoreUpgradeCost: (levels: number, p: IPlayer) => number;
|
calculateCoreUpgradeCost: (levels: number, costMult: number) => number;
|
||||||
calculateLevelUpgradeCost: (levels: number, p: IPlayer) => number;
|
calculateLevelUpgradeCost: (levels: number, costMult: number) => number;
|
||||||
calculateRamUpgradeCost: (levels: number, p: IPlayer) => number;
|
calculateRamUpgradeCost: (levels: number, costMult: number) => number;
|
||||||
purchaseCoreUpgrade: (levels: number, p: IPlayer) => boolean;
|
upgradeCore: (levels: number, prodMult: number) => void;
|
||||||
purchaseLevelUpgrade: (levels: number, p: IPlayer) => boolean;
|
upgradeLevel: (levels: number, prodMult: number) => void;
|
||||||
purchaseRamUpgrade: (levels: number, p: IPlayer) => boolean;
|
upgradeRam: (levels: number, prodMult: number) => void;
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,19 @@
|
|||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { HacknetNodeMaxLevel,
|
import {
|
||||||
HacknetNodeMaxRam,
|
HacknetNodeMaxLevel,
|
||||||
HacknetNodeMaxCores } from "../HacknetNode";
|
HacknetNodeMaxRam,
|
||||||
import { getMaxNumberLevelUpgrades,
|
HacknetNodeMaxCores
|
||||||
getMaxNumberRamUpgrades,
|
} from "../HacknetNode";
|
||||||
getMaxNumberCoreUpgrades } from "../HacknetHelpers";
|
import {
|
||||||
|
getMaxNumberLevelUpgrades,
|
||||||
|
getMaxNumberRamUpgrades,
|
||||||
|
getMaxNumberCoreUpgrades,
|
||||||
|
purchaseLevelUpgrade,
|
||||||
|
purchaseRamUpgrade,
|
||||||
|
purchaseCoreUpgrade,
|
||||||
|
} from "../HacknetHelpers";
|
||||||
|
|
||||||
import { Player } from "../../Player";
|
import { Player } from "../../Player";
|
||||||
|
|
||||||
@ -35,7 +42,7 @@ export class HacknetNode extends React.Component {
|
|||||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
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)}`;
|
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
|
||||||
if (Player.money.lt(upgradeLevelCost)) {
|
if (Player.money.lt(upgradeLevelCost)) {
|
||||||
upgradeLevelClass = "std-button-disabled";
|
upgradeLevelClass = "std-button-disabled";
|
||||||
@ -48,7 +55,7 @@ export class HacknetNode extends React.Component {
|
|||||||
if (purchaseMult === "MAX") {
|
if (purchaseMult === "MAX") {
|
||||||
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetNodeMaxLevel);
|
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetNodeMaxLevel);
|
||||||
}
|
}
|
||||||
node.purchaseLevelUpgrade(numUpgrades, Player);
|
purchaseLevelUpgrade(node, numUpgrades);
|
||||||
recalculate();
|
recalculate();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -66,7 +73,7 @@ export class HacknetNode extends React.Component {
|
|||||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
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)}`;
|
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
|
||||||
if (Player.money.lt(upgradeRamCost)) {
|
if (Player.money.lt(upgradeRamCost)) {
|
||||||
upgradeRamClass = "std-button-disabled";
|
upgradeRamClass = "std-button-disabled";
|
||||||
@ -79,7 +86,7 @@ export class HacknetNode extends React.Component {
|
|||||||
if (purchaseMult === "MAX") {
|
if (purchaseMult === "MAX") {
|
||||||
numUpgrades = getMaxNumberRamUpgrades(node, HacknetNodeMaxRam);
|
numUpgrades = getMaxNumberRamUpgrades(node, HacknetNodeMaxRam);
|
||||||
}
|
}
|
||||||
node.purchaseRamUpgrade(numUpgrades, Player);
|
purchaseRamUpgrade(node, numUpgrades);
|
||||||
recalculate();
|
recalculate();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -97,7 +104,7 @@ export class HacknetNode extends React.Component {
|
|||||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
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)}`;
|
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
|
||||||
if (Player.money.lt(upgradeCoreCost)) {
|
if (Player.money.lt(upgradeCoreCost)) {
|
||||||
upgradeCoresClass = "std-button-disabled";
|
upgradeCoresClass = "std-button-disabled";
|
||||||
@ -110,7 +117,7 @@ export class HacknetNode extends React.Component {
|
|||||||
if (purchaseMult === "MAX") {
|
if (purchaseMult === "MAX") {
|
||||||
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetNodeMaxCores);
|
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetNodeMaxCores);
|
||||||
}
|
}
|
||||||
node.purchaseCoreUpgrade(numUpgrades, Player);
|
purchaseCoreUpgrade(node, numUpgrades);
|
||||||
recalculate();
|
recalculate();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,23 @@
|
|||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { HacknetServerMaxLevel,
|
import {
|
||||||
HacknetServerMaxRam,
|
HacknetServerMaxLevel,
|
||||||
HacknetServerMaxCores,
|
HacknetServerMaxRam,
|
||||||
HacknetServerMaxCache } from "../HacknetServer";
|
HacknetServerMaxCores,
|
||||||
import { getMaxNumberLevelUpgrades,
|
HacknetServerMaxCache
|
||||||
getMaxNumberRamUpgrades,
|
} from "../HacknetServer";
|
||||||
getMaxNumberCoreUpgrades,
|
import {
|
||||||
getMaxNumberCacheUpgrades } from "../HacknetHelpers";
|
getMaxNumberLevelUpgrades,
|
||||||
|
getMaxNumberRamUpgrades,
|
||||||
|
getMaxNumberCoreUpgrades,
|
||||||
|
getMaxNumberCacheUpgrades,
|
||||||
|
purchaseLevelUpgrade,
|
||||||
|
purchaseRamUpgrade,
|
||||||
|
purchaseCoreUpgrade,
|
||||||
|
purchaseCacheUpgrade,
|
||||||
|
updateHashManagerCapacity,
|
||||||
|
} from "../HacknetHelpers";
|
||||||
|
|
||||||
import { Player } from "../../Player";
|
import { Player } from "../../Player";
|
||||||
|
|
||||||
@ -37,7 +46,7 @@ export class HacknetServer extends React.Component {
|
|||||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
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)}`;
|
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
|
||||||
if (Player.money.lt(upgradeLevelCost)) {
|
if (Player.money.lt(upgradeLevelCost)) {
|
||||||
upgradeLevelClass = "std-button-disabled";
|
upgradeLevelClass = "std-button-disabled";
|
||||||
@ -50,7 +59,7 @@ export class HacknetServer extends React.Component {
|
|||||||
if (purchaseMult === "MAX") {
|
if (purchaseMult === "MAX") {
|
||||||
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetServerMaxLevel);
|
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetServerMaxLevel);
|
||||||
}
|
}
|
||||||
node.purchaseLevelUpgrade(numUpgrades, Player);
|
purchaseLevelUpgrade(node, numUpgrades);
|
||||||
recalculate();
|
recalculate();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -69,7 +78,7 @@ export class HacknetServer extends React.Component {
|
|||||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
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)}`;
|
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
|
||||||
if (Player.money.lt(upgradeRamCost)) {
|
if (Player.money.lt(upgradeRamCost)) {
|
||||||
upgradeRamClass = "std-button-disabled";
|
upgradeRamClass = "std-button-disabled";
|
||||||
@ -82,7 +91,7 @@ export class HacknetServer extends React.Component {
|
|||||||
if (purchaseMult === "MAX") {
|
if (purchaseMult === "MAX") {
|
||||||
numUpgrades = getMaxNumberRamUpgrades(node, HacknetServerMaxRam);
|
numUpgrades = getMaxNumberRamUpgrades(node, HacknetServerMaxRam);
|
||||||
}
|
}
|
||||||
node.purchaseRamUpgrade(numUpgrades, Player);
|
purchaseRamUpgrade(node, numUpgrades);
|
||||||
recalculate();
|
recalculate();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -101,7 +110,7 @@ export class HacknetServer extends React.Component {
|
|||||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
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)}`;
|
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
|
||||||
if (Player.money.lt(upgradeCoreCost)) {
|
if (Player.money.lt(upgradeCoreCost)) {
|
||||||
upgradeCoresClass = "std-button-disabled";
|
upgradeCoresClass = "std-button-disabled";
|
||||||
@ -114,7 +123,7 @@ export class HacknetServer extends React.Component {
|
|||||||
if (purchaseMult === "MAX") {
|
if (purchaseMult === "MAX") {
|
||||||
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetServerMaxCores);
|
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetServerMaxCores);
|
||||||
}
|
}
|
||||||
node.purchaseCoreUpgrade(numUpgrades, Player);
|
purchaseCoreUpgrade(node, numUpgrades);
|
||||||
recalculate();
|
recalculate();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -146,9 +155,9 @@ export class HacknetServer extends React.Component {
|
|||||||
if (purchaseMult === "MAX") {
|
if (purchaseMult === "MAX") {
|
||||||
numUpgrades = getMaxNumberCacheUpgrades(node, HacknetServerMaxCache);
|
numUpgrades = getMaxNumberCacheUpgrades(node, HacknetServerMaxCache);
|
||||||
}
|
}
|
||||||
node.purchaseCacheUpgrade(numUpgrades, Player);
|
purchaseCacheUpgrade(node, numUpgrades);
|
||||||
recalculate();
|
recalculate();
|
||||||
Player.hashManager.updateCapacity(Player);
|
updateHashManagerCapacity();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,37 +2,42 @@
|
|||||||
* Location and traveling-related helper functions.
|
* Location and traveling-related helper functions.
|
||||||
* Mostly used for UI
|
* Mostly used for UI
|
||||||
*/
|
*/
|
||||||
import { CONSTANTS } from "../Constants";
|
import { CONSTANTS } from "../Constants";
|
||||||
|
|
||||||
import { CityName } from "./data/CityNames";
|
import { CityName } from "./data/CityNames";
|
||||||
|
|
||||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
import { AllServers,
|
import {
|
||||||
AddToAllServers } from "../Server/AllServers";
|
AddToAllServers,
|
||||||
import { Server } from "../Server/Server";
|
createUniqueRandomIp,
|
||||||
import { getPurchaseServerCost,
|
} from "../Server/AllServers";
|
||||||
purchaseRamForHomeComputer,
|
import { safetlyCreateUniqueServer } from "../Server/ServerHelpers";
|
||||||
purchaseServer } from "../Server/ServerPurchases";
|
import {
|
||||||
import { SpecialServerIps } from "../Server/SpecialServerIps";
|
getPurchaseServerCost,
|
||||||
import { Settings } from "../Settings/Settings";
|
purchaseRamForHomeComputer,
|
||||||
|
purchaseServer
|
||||||
|
} from "../Server/ServerPurchases";
|
||||||
|
import { SpecialServerIps } from "../Server/SpecialServerIps";
|
||||||
|
import { Settings } from "../Settings/Settings";
|
||||||
|
|
||||||
import { numeralWrapper } from "../ui/numeralFormat";
|
import { numeralWrapper } from "../ui/numeralFormat";
|
||||||
|
|
||||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||||
import { createRandomIp } from "../../utils/IPAddress";
|
import {
|
||||||
import { yesNoBoxGetYesButton,
|
yesNoBoxGetYesButton,
|
||||||
yesNoBoxGetNoButton,
|
yesNoBoxGetNoButton,
|
||||||
yesNoBoxClose,
|
yesNoBoxClose,
|
||||||
yesNoBoxCreate,
|
yesNoBoxCreate,
|
||||||
yesNoTxtInpBoxGetYesButton,
|
yesNoTxtInpBoxGetYesButton,
|
||||||
yesNoTxtInpBoxGetNoButton,
|
yesNoTxtInpBoxGetNoButton,
|
||||||
yesNoTxtInpBoxClose,
|
yesNoTxtInpBoxClose,
|
||||||
yesNoTxtInpBoxCreate } from "../../utils/YesNoBox";
|
yesNoTxtInpBoxCreate
|
||||||
|
} from "../../utils/YesNoBox";
|
||||||
|
|
||||||
import { createElement } from "../../utils/uiHelpers/createElement";
|
import { createElement } from "../../utils/uiHelpers/createElement";
|
||||||
import { createPopup } from "../../utils/uiHelpers/createPopup";
|
import { createPopup } from "../../utils/uiHelpers/createPopup";
|
||||||
import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton";
|
import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton";
|
||||||
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
|
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a pop-up box that lets the player confirm traveling to a different city
|
* Create a pop-up box that lets the player confirm traveling to a different city
|
||||||
@ -271,8 +276,8 @@ export function purchaseTorRouter(p: IPlayer) {
|
|||||||
}
|
}
|
||||||
p.loseMoney(CONSTANTS.TorRouterCost);
|
p.loseMoney(CONSTANTS.TorRouterCost);
|
||||||
|
|
||||||
const darkweb = new Server({
|
const darkweb = safetlyCreateUniqueServer({
|
||||||
ip: createRandomIp(), hostname:"darkweb", organizationName:"",
|
ip: createUniqueRandomIp(), hostname:"darkweb", organizationName:"",
|
||||||
isConnectedTo:false, adminRights:false, purchasedByPlayer:false, maxRam:1
|
isConnectedTo:false, adminRights:false, purchasedByPlayer:false, maxRam:1
|
||||||
});
|
});
|
||||||
AddToAllServers(darkweb);
|
AddToAllServers(darkweb);
|
||||||
|
72
src/Netscript/Environment.ts
Normal file
72
src/Netscript/Environment.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
332
src/Netscript/RamCostGenerator.ts
Normal file
332
src/Netscript/RamCostGenerator.ts
Normal file
@ -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;
|
||||||
|
}
|
176
src/Netscript/WorkerScript.ts
Normal file
176
src/Netscript/WorkerScript.ts
Normal file
@ -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 { WorkerScript } from "./Netscript/WorkerScript";
|
||||||
import { CONSTANTS } from "./Constants";
|
|
||||||
import { Player } from "./Player";
|
|
||||||
import { Environment } from "./NetscriptEnvironment";
|
|
||||||
import { WorkerScript, addWorkerScript } from "./NetscriptWorker";
|
|
||||||
import { Server } from "./Server/Server";
|
|
||||||
import { getServer } from "./Server/ServerHelpers";
|
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 { setTimeoutRef } from "./utils/SetTimeoutRef";
|
||||||
import { parse, Node } from "../utils/acorn";
|
import { parse, Node } from "../utils/acorn";
|
||||||
|
|
||||||
import { arrayToString } from "../utils/helpers/arrayToString";
|
|
||||||
import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
|
import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
|
||||||
import { isString } from "../utils/helpers/isString";
|
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) {
|
export function killNetscriptDelay(workerScript) {
|
||||||
if (workerScript instanceof WorkerScript) {
|
if (workerScript instanceof WorkerScript) {
|
||||||
if (workerScript.delay) {
|
if (workerScript.delay) {
|
||||||
@ -155,60 +35,6 @@ export function makeRuntimeRejectMsg(workerScript, msg, exp=null) {
|
|||||||
return "|"+workerScript.serverIp+"|"+workerScript.name+"|" + msg + lineNum;
|
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) {
|
export function getErrorLineNumber(exp, workerScript) {
|
||||||
var code = workerScript.scriptRef.codeCode();
|
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 {
|
import {
|
||||||
addActiveScriptsItem,
|
addActiveScriptsItem,
|
||||||
deleteActiveScriptsItem,
|
deleteActiveScriptsItem,
|
||||||
@ -6,9 +12,7 @@ import {
|
|||||||
import { CONSTANTS } from "./Constants";
|
import { CONSTANTS } from "./Constants";
|
||||||
import { Engine } from "./engine";
|
import { Engine } from "./engine";
|
||||||
import { Interpreter } from "./JSInterpreter";
|
import { Interpreter } from "./JSInterpreter";
|
||||||
import { Environment } from "./NetscriptEnvironment";
|
|
||||||
import {
|
import {
|
||||||
evaluate,
|
|
||||||
isScriptErrorMessage,
|
isScriptErrorMessage,
|
||||||
makeRuntimeRejectMsg,
|
makeRuntimeRejectMsg,
|
||||||
killNetscriptDelay
|
killNetscriptDelay
|
||||||
@ -16,6 +20,11 @@ import {
|
|||||||
import { NetscriptFunctions } from "./NetscriptFunctions";
|
import { NetscriptFunctions } from "./NetscriptFunctions";
|
||||||
import { executeJSScript } from "./NetscriptJSEvaluator";
|
import { executeJSScript } from "./NetscriptJSEvaluator";
|
||||||
import { NetscriptPort } from "./NetscriptPort";
|
import { NetscriptPort } from "./NetscriptPort";
|
||||||
|
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
|
||||||
|
import {
|
||||||
|
findRunningScript,
|
||||||
|
scriptCalculateOfflineProduction,
|
||||||
|
} from "./Script/ScriptHelpers";
|
||||||
import { AllServers } from "./Server/AllServers";
|
import { AllServers } from "./Server/AllServers";
|
||||||
import { Settings } from "./Settings/Settings";
|
import { Settings } from "./Settings/Settings";
|
||||||
import { setTimeoutRef } from "./utils/SetTimeoutRef";
|
import { setTimeoutRef } from "./utils/SetTimeoutRef";
|
||||||
@ -29,79 +38,17 @@ import { arrayToString } from "../utils/helpers/arrayToString";
|
|||||||
import { roundToTwo } from "../utils/helpers/roundToTwo";
|
import { roundToTwo } from "../utils/helpers/roundToTwo";
|
||||||
import { isString } from "../utils/StringHelperFunctions";
|
import { isString } from "../utils/StringHelperFunctions";
|
||||||
|
|
||||||
|
|
||||||
const walk = require("acorn/dist/walk");
|
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
|
//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) {
|
for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
|
||||||
NetscriptPorts.push(new NetscriptPort());
|
NetscriptPorts.push(new NetscriptPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
function prestigeWorkerScripts() {
|
export function prestigeWorkerScripts() {
|
||||||
for (var i = 0; i < workerScripts.length; ++i) {
|
for (var i = 0; i < workerScripts.length; ++i) {
|
||||||
deleteActiveScriptsItem(workerScripts[i]);
|
deleteActiveScriptsItem(workerScripts[i]);
|
||||||
workerScripts[i].env.stopFlag = true;
|
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
|
//Loop through workerScripts and run every script that is not currently running
|
||||||
function runScriptsLoop() {
|
export function runScriptsLoop() {
|
||||||
var scriptDeleted = false;
|
var scriptDeleted = false;
|
||||||
|
|
||||||
//Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing
|
//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);
|
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.
|
* Queues a script to be killed by setting its stop flag to true. This
|
||||||
//The runScriptsLoop() will then delete the script from worker scripts
|
* kills and timed/blocking Netscript functions (like hack(), sleep(), etc.) and
|
||||||
function killWorkerScript(runningScriptObj, serverIp) {
|
* 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++) {
|
for (var i = 0; i < workerScripts.length; i++) {
|
||||||
if (workerScripts[i].name == runningScriptObj.filename && workerScripts[i].serverIp == serverIp &&
|
if (workerScripts[i].name == runningScriptObj.filename && workerScripts[i].serverIp == serverIp &&
|
||||||
compareArrays(workerScripts[i].args, runningScriptObj.args)) {
|
compareArrays(workerScripts[i].args, runningScriptObj.args)) {
|
||||||
workerScripts[i].env.stopFlag = true;
|
workerScripts[i].env.stopFlag = true;
|
||||||
killNetscriptDelay(workerScripts[i]);
|
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 true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
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;
|
var filename = runningScriptObj.filename;
|
||||||
|
|
||||||
//Update server's ram usage
|
//Update server's ram usage
|
||||||
@ -596,7 +541,7 @@ function addWorkerScript(runningScriptObj, server) {
|
|||||||
} else {
|
} else {
|
||||||
runningScriptObj.threads = 1;
|
runningScriptObj.threads = 1;
|
||||||
}
|
}
|
||||||
var ramUsage = roundToTwo(runningScriptObj.getRamUsage() * threads);
|
var ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads);
|
||||||
var ramAvailable = server.maxRam - server.ramUsed;
|
var ramAvailable = server.maxRam - server.ramUsed;
|
||||||
if (ramUsage > ramAvailable) {
|
if (ramUsage > ramAvailable) {
|
||||||
dialogBoxCreate("Not enough RAM to run script " + runningScriptObj.filename + " with args " +
|
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);
|
server.ramUsed = roundToTwo(server.ramUsed + ramUsage);
|
||||||
|
|
||||||
//Create the WorkerScript
|
//Create the WorkerScript
|
||||||
var s = new WorkerScript(runningScriptObj);
|
var s = new WorkerScript(runningScriptObj, NetscriptFunctions);
|
||||||
s.ramUsage = ramUsage;
|
s.ramUsage = ramUsage;
|
||||||
|
|
||||||
//Add the WorkerScript to the Active Scripts list
|
//Add the WorkerScript to the Active Scripts list
|
||||||
@ -619,14 +564,105 @@ function addWorkerScript(runningScriptObj, server) {
|
|||||||
return;
|
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
|
var time = (numCycles * Engine._idleSpeed) / 1000; //seconds
|
||||||
for (var i = 0; i < workerScripts.length; ++i) {
|
for (var i = 0; i < workerScripts.length; ++i) {
|
||||||
workerScripts[i].scriptRef.onlineRunningTime += time;
|
workerScripts[i].scriptRef.onlineRunningTime += time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {WorkerScript, workerScripts, NetscriptPorts, runScriptsLoop,
|
/**
|
||||||
killWorkerScript, addWorkerScript, updateOnlineScriptTimes,
|
* Called when the game is loaded. Loads all running scripts (from all servers)
|
||||||
prestigeWorkerScripts};
|
* 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[];
|
purchasedServers: any[];
|
||||||
queuedAugmentations: IPlayerOwnedAugmentation[];
|
queuedAugmentations: IPlayerOwnedAugmentation[];
|
||||||
resleeves: Resleeve[];
|
resleeves: Resleeve[];
|
||||||
|
scriptProdSinceLastAug: number;
|
||||||
sleeves: Sleeve[];
|
sleeves: Sleeve[];
|
||||||
sleevesFromCovenant: number;
|
sleevesFromCovenant: number;
|
||||||
sourceFiles: IPlayerOwnedSourceFile[];
|
sourceFiles: IPlayerOwnedSourceFile[];
|
||||||
|
@ -1,60 +1,69 @@
|
|||||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||||
import { applyAugmentation } from "../../Augmentation/AugmentationHelpers";
|
import { applyAugmentation } from "../../Augmentation/AugmentationHelpers";
|
||||||
import { PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
|
import { PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
|
||||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||||
import { Bladeburner } from "../../Bladeburner";
|
import { Bladeburner } from "../../Bladeburner";
|
||||||
import { CodingContractRewardType } from "../../CodingContracts";
|
import { CodingContractRewardType } from "../../CodingContracts";
|
||||||
import { Company } from "../../Company/Company";
|
import { Company } from "../../Company/Company";
|
||||||
import { Companies } from "../../Company/Companies";
|
import { Companies } from "../../Company/Companies";
|
||||||
import { getNextCompanyPositionHelper } from "../../Company/GetNextCompanyPosition";
|
import { getNextCompanyPositionHelper } from "../../Company/GetNextCompanyPosition";
|
||||||
import { getJobRequirementText } from "../../Company/GetJobRequirementText";
|
import { getJobRequirementText } from "../../Company/GetJobRequirementText";
|
||||||
import { CompanyPositions } from "../../Company/CompanyPositions";
|
import { CompanyPositions } from "../../Company/CompanyPositions";
|
||||||
import * as posNames from "../../Company/data/companypositionnames";
|
import * as posNames from "../../Company/data/companypositionnames";
|
||||||
import {CONSTANTS} from "../../Constants";
|
import {CONSTANTS} from "../../Constants";
|
||||||
import { Corporation } from "../../Corporation/Corporation";
|
import { Corporation } from "../../Corporation/Corporation";
|
||||||
import { Programs } from "../../Programs/Programs";
|
import { Programs } from "../../Programs/Programs";
|
||||||
import { determineCrimeSuccess } from "../../Crime/CrimeHelpers";
|
import { determineCrimeSuccess } from "../../Crime/CrimeHelpers";
|
||||||
import { Crimes } from "../../Crime/Crimes";
|
import { Crimes } from "../../Crime/Crimes";
|
||||||
import {Engine} from "../../engine";
|
import { Engine } from "../../engine";
|
||||||
import { Faction } from "../../Faction/Faction";
|
import { Faction } from "../../Faction/Faction";
|
||||||
import { Factions } from "../../Faction/Factions";
|
import { Factions } from "../../Faction/Factions";
|
||||||
import { displayFactionContent } from "../../Faction/FactionHelpers";
|
import { displayFactionContent } from "../../Faction/FactionHelpers";
|
||||||
import { resetGangs } from "../../Gang";
|
import { resetGangs } from "../../Gang";
|
||||||
import { hasHacknetServers } from "../../Hacknet/HacknetHelpers";
|
import { hasHacknetServers } from "../../Hacknet/HacknetHelpers";
|
||||||
import { HashManager } from "../../Hacknet/HashManager";
|
import { HashManager } from "../../Hacknet/HashManager";
|
||||||
import { Cities } from "../../Locations/Cities";
|
import { Cities } from "../../Locations/Cities";
|
||||||
import { Locations } from "../../Locations/Locations";
|
import { Locations } from "../../Locations/Locations";
|
||||||
import { CityName } from "../../Locations/data/CityNames";
|
import { CityName } from "../../Locations/data/CityNames";
|
||||||
import { LocationName } from "../../Locations/data/LocationNames";
|
import { LocationName } from "../../Locations/data/LocationNames";
|
||||||
import {hasBn11SF, hasWallStreetSF,hasAISF} from "../../NetscriptFunctions";
|
import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve";
|
||||||
import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve";
|
import {
|
||||||
import { AllServers,
|
AllServers,
|
||||||
AddToAllServers } from "../../Server/AllServers";
|
AddToAllServers,
|
||||||
import { Server } from "../../Server/Server";
|
createUniqueRandomIp,
|
||||||
import {Settings} from "../../Settings/Settings";
|
} from "../../Server/AllServers";
|
||||||
import {SpecialServerIps, SpecialServerNames} from "../../Server/SpecialServerIps";
|
import { safetlyCreateUniqueServer } from "../../Server/ServerHelpers";
|
||||||
import {SourceFiles, applySourceFile} from "../../SourceFile";
|
import { Settings } from "../../Settings/Settings";
|
||||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
import { SpecialServerIps, SpecialServerNames } from "../../Server/SpecialServerIps";
|
||||||
|
import { SourceFiles, applySourceFile } from "../../SourceFile";
|
||||||
|
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||||
|
|
||||||
import Decimal from "decimal.js";
|
import Decimal from "decimal.js";
|
||||||
|
|
||||||
import {numeralWrapper} from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
|
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
|
||||||
import {dialogBoxCreate} from "../../../utils/DialogBox";
|
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||||
import {clearEventListeners} from "../../../utils/uiHelpers/clearEventListeners";
|
import { clearEventListeners } from "../../../utils/uiHelpers/clearEventListeners";
|
||||||
import {createRandomIp} from "../../../utils/IPAddress";
|
import {
|
||||||
import {Reviver, Generic_toJSON,
|
Reviver,
|
||||||
Generic_fromJSON} from "../../../utils/JSONReviver";
|
Generic_toJSON,
|
||||||
import {convertTimeMsToTimeElapsedString} from "../../../utils/StringHelperFunctions";
|
Generic_fromJSON,
|
||||||
|
} from "../../../utils/JSONReviver";
|
||||||
|
import {convertTimeMsToTimeElapsedString} from "../../../utils/StringHelperFunctions";
|
||||||
|
|
||||||
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
|
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
|
||||||
|
|
||||||
export function init() {
|
export function init() {
|
||||||
/* Initialize Player's home computer */
|
/* Initialize Player's home computer */
|
||||||
var t_homeComp = new Server({
|
var t_homeComp = safetlyCreateUniqueServer({
|
||||||
ip:createRandomIp(), hostname:"home", organizationName:"Home PC",
|
adminRights: true,
|
||||||
isConnectedTo:true, adminRights:true, purchasedByPlayer:true, maxRam:8
|
hostname: "home",
|
||||||
|
ip: createUniqueRandomIp(),
|
||||||
|
isConnectedTo: true,
|
||||||
|
maxRam: 8,
|
||||||
|
organizationName: "Home PC",
|
||||||
|
purchasedByPlayer: true,
|
||||||
});
|
});
|
||||||
this.homeComputer = t_homeComp.ip;
|
this.homeComputer = t_homeComp.ip;
|
||||||
this.currentServer = t_homeComp.ip;
|
this.currentServer = t_homeComp.ip;
|
||||||
@ -150,7 +159,7 @@ export function prestigeAugmentation() {
|
|||||||
this.moneySourceA.reset();
|
this.moneySourceA.reset();
|
||||||
|
|
||||||
this.hacknetNodes.length = 0;
|
this.hacknetNodes.length = 0;
|
||||||
this.hashManager.prestige(this);
|
this.hashManager.prestige();
|
||||||
|
|
||||||
// Re-calculate skills and reset HP
|
// Re-calculate skills and reset HP
|
||||||
this.updateSkillLevels();
|
this.updateSkillLevels();
|
||||||
@ -240,7 +249,7 @@ export function prestigeSourceFile() {
|
|||||||
this.lastUpdate = new Date().getTime();
|
this.lastUpdate = new Date().getTime();
|
||||||
|
|
||||||
this.hacknetNodes.length = 0;
|
this.hacknetNodes.length = 0;
|
||||||
this.hashManager.prestige(this);
|
this.hashManager.prestige();
|
||||||
|
|
||||||
// Gang
|
// Gang
|
||||||
this.gang = null;
|
this.gang = null;
|
||||||
@ -454,7 +463,7 @@ export function gainIntelligenceExp(exp) {
|
|||||||
if (isNaN(exp)) {
|
if (isNaN(exp)) {
|
||||||
console.log("ERROR: NaN passed into Player.gainIntelligenceExp()"); return;
|
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;
|
this.intelligence_exp += exp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -943,7 +952,7 @@ export function getWorkMoneyGain() {
|
|||||||
// If player has SF-11, calculate salary multiplier from favor
|
// If player has SF-11, calculate salary multiplier from favor
|
||||||
let bn11Mult = 1;
|
let bn11Mult = 1;
|
||||||
const company = Companies[this.companyName];
|
const company = Companies[this.companyName];
|
||||||
if (hasBn11SF) { bn11Mult = 1 + (company.favor / 100); }
|
if (SourceFileFlags[11] > 0) { bn11Mult = 1 + (company.favor / 100); }
|
||||||
|
|
||||||
// Get base salary
|
// Get base salary
|
||||||
const companyPositionName = this.jobs[this.companyName];
|
const companyPositionName = this.jobs[this.companyName];
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
/**
|
/**
|
||||||
* Server and HacknetServer-related methods for the Player class (PlayerObject)
|
* Server and HacknetServer-related methods for the Player class (PlayerObject)
|
||||||
*/
|
*/
|
||||||
import { IPlayer } from "../IPlayer";
|
import { IPlayer } from "../IPlayer";
|
||||||
|
|
||||||
import { CONSTANTS } from "../../Constants";
|
import { CONSTANTS } from "../../Constants";
|
||||||
|
|
||||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||||
import { HacknetServer } from "../../Hacknet/HacknetServer";
|
import { HacknetServer } from "../../Hacknet/HacknetServer";
|
||||||
import { AddToAllServers,
|
import {
|
||||||
AllServers } from "../../Server/AllServers";
|
AddToAllServers,
|
||||||
import { SpecialServerIps } from "../../Server/SpecialServerIps";
|
AllServers,
|
||||||
|
createUniqueRandomIp,
|
||||||
|
} from "../../Server/AllServers";
|
||||||
|
import { SpecialServerIps } from "../../Server/SpecialServerIps";
|
||||||
|
|
||||||
export function hasTorRouter(this: IPlayer) {
|
export function hasTorRouter(this: IPlayer) {
|
||||||
return SpecialServerIps.hasOwnProperty("Darkweb Server");
|
return SpecialServerIps.hasOwnProperty("Darkweb Server");
|
||||||
@ -41,7 +44,8 @@ export function createHacknetServer(this: IPlayer): HacknetServer {
|
|||||||
const server = new HacknetServer({
|
const server = new HacknetServer({
|
||||||
adminRights: true,
|
adminRights: true,
|
||||||
hostname: name,
|
hostname: name,
|
||||||
player: this,
|
ip: createUniqueRandomIp(),
|
||||||
|
// player: this,
|
||||||
});
|
});
|
||||||
this.hacknetNodes.push(server.ip);
|
this.hacknetNodes.push(server.ip);
|
||||||
|
|
||||||
|
@ -14,8 +14,6 @@ import { Person,
|
|||||||
createTaskTracker } from "../Person";
|
createTaskTracker } from "../Person";
|
||||||
|
|
||||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
|
||||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
|
||||||
|
|
||||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||||
|
|
||||||
|
3
src/Player.d.ts
vendored
Normal file
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 { Factions, initFactions } from "./Faction/Factions";
|
||||||
import { joinFaction } from "./Faction/FactionHelpers";
|
import { joinFaction } from "./Faction/FactionHelpers";
|
||||||
import { deleteGangDisplayContent } from "./Gang";
|
import { deleteGangDisplayContent } from "./Gang";
|
||||||
|
import { updateHashManagerCapacity } from "./Hacknet/HacknetHelpers";
|
||||||
import { Message } from "./Message/Message";
|
import { Message } from "./Message/Message";
|
||||||
import { initMessages, Messages } from "./Message/MessageHelpers";
|
import { initMessages, Messages } from "./Message/MessageHelpers";
|
||||||
import { initSingularitySFFlags, hasWallStreetSF } from "./NetscriptFunctions";
|
import { prestigeWorkerScripts } from "./NetscriptWorker";
|
||||||
import {
|
|
||||||
WorkerScript,
|
|
||||||
workerScripts,
|
|
||||||
prestigeWorkerScripts
|
|
||||||
} from "./NetscriptWorker";
|
|
||||||
import { Player } from "./Player";
|
import { Player } from "./Player";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -152,7 +148,7 @@ function prestigeAugmentation() {
|
|||||||
|
|
||||||
// BitNode 8: Ghost of Wall Street
|
// BitNode 8: Ghost of Wall Street
|
||||||
if (Player.bitNodeN === 8) {Player.money = new Decimal(BitNode8StartingMoney);}
|
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.hasWseAccount = true;
|
||||||
Player.hasTixApiAccess = true;
|
Player.hasTixApiAccess = true;
|
||||||
}
|
}
|
||||||
@ -257,9 +253,6 @@ function prestigeSourceFile() {
|
|||||||
Terminal.resetTerminalInput();
|
Terminal.resetTerminalInput();
|
||||||
Engine.loadTerminalContent();
|
Engine.loadTerminalContent();
|
||||||
|
|
||||||
// Reinitialize Bit Node flags
|
|
||||||
initSingularitySFFlags();
|
|
||||||
|
|
||||||
// BitNode 3: Corporatocracy
|
// BitNode 3: Corporatocracy
|
||||||
if (Player.bitNodeN === 3) {
|
if (Player.bitNodeN === 3) {
|
||||||
homeComp.messages.push("corporation-management-handbook.lit");
|
homeComp.messages.push("corporation-management-handbook.lit");
|
||||||
@ -313,7 +306,7 @@ function prestigeSourceFile() {
|
|||||||
|
|
||||||
// BitNode 8: Ghost of Wall Street
|
// BitNode 8: Ghost of Wall Street
|
||||||
if (Player.bitNodeN === 8) {Player.money = new Decimal(BitNode8StartingMoney);}
|
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.hasWseAccount = true;
|
||||||
Player.hasTixApiAccess = true;
|
Player.hasTixApiAccess = true;
|
||||||
}
|
}
|
||||||
@ -345,7 +338,7 @@ function prestigeSourceFile() {
|
|||||||
hserver.cache = 5;
|
hserver.cache = 5;
|
||||||
hserver.updateHashRate(Player);
|
hserver.updateHashRate(Player);
|
||||||
hserver.updateHashCapacity();
|
hserver.updateHashCapacity();
|
||||||
Player.hashManager.updateCapacity(Player);
|
updateHashManagerCapacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh Main Menu (the 'World' menu, specifically)
|
// Refresh Main Menu (the 'World' menu, specifically)
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
// Calculate a script's RAM usage
|
// Calculate a script's RAM usage
|
||||||
const walk = require("acorn/dist/walk"); // Importing this doesn't work for some reason.
|
const walk = require("acorn/dist/walk"); // Importing this doesn't work for some reason.
|
||||||
|
|
||||||
import { CONSTANTS } from "../Constants";
|
import { RamCosts, RamCostConstants } from "../Netscript/RamCostGenerator";
|
||||||
import {evaluateImport} from "../NetscriptEvaluator";
|
import { parse, Node } from "../../utils/acorn";
|
||||||
import { WorkerScript } from "../NetscriptWorker";
|
|
||||||
import { Player } from "../Player";
|
|
||||||
import {parse, Node} from "../../utils/acorn";
|
|
||||||
|
|
||||||
// These special strings are used to reference the presence of a given logical
|
// These special strings are used to reference the presence of a given logical
|
||||||
// construct within a user script.
|
// construct within a user script.
|
||||||
@ -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
|
// 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__.
|
// 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 unresolvedRefs = Object.keys(dependencyMap).filter(s => s.startsWith(initialModule));
|
||||||
const resolvedRefs = new Set();
|
const resolvedRefs = new Set();
|
||||||
while (unresolvedRefs.length > 0) {
|
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.
|
// Check if this is one of the special keys, and add the appropriate ram cost if so.
|
||||||
if (ref === "hacknet" && !resolvedRefs.has("hacknet")) {
|
if (ref === "hacknet" && !resolvedRefs.has("hacknet")) {
|
||||||
ram += CONSTANTS.ScriptHacknetNodesRamCost;
|
ram += RamCostConstants.ScriptHacknetNodesRamCost;
|
||||||
}
|
}
|
||||||
if (ref === "document" && !resolvedRefs.has("document")) {
|
if (ref === "document" && !resolvedRefs.has("document")) {
|
||||||
ram += CONSTANTS.ScriptDomRamCost;
|
ram += RamCostConstants.ScriptDomRamCost;
|
||||||
}
|
}
|
||||||
if (ref === "window" && !resolvedRefs.has("window")) {
|
if (ref === "window" && !resolvedRefs.has("window")) {
|
||||||
ram += CONSTANTS.ScriptDomRamCost;
|
ram += RamCostConstants.ScriptDomRamCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvedRefs.add(ref);
|
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
|
// Check if this identifier is a function in the workerscript env.
|
||||||
// get its RAM cost. We do this by calling it, which works because the running script
|
// If it is, then we need to get its RAM cost.
|
||||||
// is in checkingRam mode.
|
|
||||||
//
|
//
|
||||||
// TODO it would be simpler to just reference a dictionary.
|
// TODO it would be simpler to just reference a dictionary.
|
||||||
try {
|
try {
|
||||||
@ -152,8 +148,15 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Special logic for namespaces (Bladeburner, CodingCOntract)
|
// Only count each function once
|
||||||
var func;
|
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) {
|
if (ref in workerScript.env.vars.bladeburner) {
|
||||||
func = workerScript.env.vars.bladeburner[ref];
|
func = workerScript.env.vars.bladeburner[ref];
|
||||||
} else if (ref in workerScript.env.vars.codingcontract) {
|
} 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) {
|
} else if (ref in workerScript.env.vars.sleeve) {
|
||||||
func = workerScript.env.vars.sleeve[ref];
|
func = workerScript.env.vars.sleeve[ref];
|
||||||
} else {
|
} else {
|
||||||
func = workerScript.env.get(ref);
|
func = workerScript.env.vars[ref];
|
||||||
}
|
}
|
||||||
ram += applyFuncRam(func);
|
ram += applyFuncRam(func);
|
||||||
} catch (error) {continue;}
|
} catch (error) {continue;}
|
||||||
@ -309,103 +312,23 @@ function parseOnlyCalculateDeps(code, currentModule) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function calculateRamUsage(codeCopy) {
|
export async function calculateRamUsage(codeCopy) {
|
||||||
//Create a temporary/mock WorkerScript and an AST from the code
|
// We don't need a real WorkerScript for this. Just an object that keeps
|
||||||
var currServ = Player.getCurrentServer();
|
// track of whatever's needed for RAM calculations
|
||||||
var workerScript = new WorkerScript({
|
const workerScript = {
|
||||||
filename:"foo",
|
loadedFns: {},
|
||||||
scriptRef: {code:""},
|
serverIp: currServ.ip,
|
||||||
args:[],
|
env: {
|
||||||
getCode: function() { return ""; }
|
vars: RamCosts,
|
||||||
});
|
}
|
||||||
workerScript.checkingRam = true; //Netscript functions will return RAM usage
|
}
|
||||||
workerScript.serverIp = currServ.ip;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await parseOnlyRamCalculate(currServ, codeCopy, workerScript);
|
return await parseOnlyRamCalculate(currServ, codeCopy, workerScript);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Failed to parse ram using new method. Falling back.", e);
|
console.error(`Failed to parse script for RAM calculations:`);
|
||||||
|
console.error(e);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try the old way.
|
return -1;
|
||||||
|
|
||||||
try {
|
|
||||||
var ast = parse(codeCopy, {sourceType:"module"});
|
|
||||||
} catch(e) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Search through AST, scanning for any 'Identifier' nodes for functions, or While/For/If nodes
|
|
||||||
var queue = [], ramUsage = CONSTANTS.ScriptBaseRamCost;
|
|
||||||
var whileUsed = false, forUsed = false, ifUsed = false;
|
|
||||||
queue.push(ast);
|
|
||||||
while (queue.length != 0) {
|
|
||||||
var exp = queue.shift();
|
|
||||||
switch (exp.type) {
|
|
||||||
case "ImportDeclaration":
|
|
||||||
//Gets an array of all imported functions as AST expressions
|
|
||||||
//and pushes them on the queue.
|
|
||||||
var res = evaluateImport(exp, workerScript, true);
|
|
||||||
for (var i = 0; i < res.length; ++i) {
|
|
||||||
queue.push(res[i]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "BlockStatement":
|
|
||||||
case "Program":
|
|
||||||
for (var i = 0; i < exp.body.length; ++i) {
|
|
||||||
if (exp.body[i] instanceof Node) {
|
|
||||||
queue.push(exp.body[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "WhileStatement":
|
|
||||||
if (!whileUsed) {
|
|
||||||
ramUsage += CONSTANTS.ScriptWhileRamCost;
|
|
||||||
whileUsed = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "ForStatement":
|
|
||||||
if (!forUsed) {
|
|
||||||
ramUsage += CONSTANTS.ScriptForRamCost;
|
|
||||||
forUsed = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "IfStatement":
|
|
||||||
if (!ifUsed) {
|
|
||||||
ramUsage += CONSTANTS.ScriptIfRamCost;
|
|
||||||
ifUsed = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Identifier":
|
|
||||||
if (exp.name in workerScript.env.vars) {
|
|
||||||
var func = workerScript.env.get(exp.name);
|
|
||||||
if (typeof func === "function") {
|
|
||||||
try {
|
|
||||||
var res = func.apply(null, []);
|
|
||||||
if (typeof res === "number") {
|
|
||||||
ramUsage += res;
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
console.log("ERROR applying function: " + e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var prop in exp) {
|
|
||||||
if (exp.hasOwnProperty(prop)) {
|
|
||||||
if (exp[prop] instanceof Node) {
|
|
||||||
queue.push(exp[prop]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Special case: hacknetnodes array
|
|
||||||
if (codeCopy.includes("hacknet")) {
|
|
||||||
ramUsage += CONSTANTS.ScriptHacknetNodesRamCost;
|
|
||||||
}
|
|
||||||
return ramUsage;
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
// Class representing a Script instance that is actively running.
|
// Class representing a Script instance that is actively running.
|
||||||
// A Script can have multiple active instances
|
// A Script can have multiple active instances
|
||||||
import { Script } from "./Script";
|
import { Script } from "./Script";
|
||||||
import { FconfSettings } from "../Fconf/FconfSettings";
|
import { FconfSettings } from "../Fconf/FconfSettings";
|
||||||
import { AllServers } from "../Server/AllServers";
|
import { Settings } from "../Settings/Settings";
|
||||||
import { Settings } from "../Settings/Settings";
|
import { IMap } from "../types";
|
||||||
import { IMap } from "../types";
|
import { post } from "../ui/postToTerminal";
|
||||||
import { post } from "../ui/postToTerminal";
|
|
||||||
|
|
||||||
import { Generic_fromJSON,
|
import {
|
||||||
Generic_toJSON,
|
Generic_fromJSON,
|
||||||
Reviver } from "../../utils/JSONReviver";
|
Generic_toJSON,
|
||||||
import { getTimestamp } from "../../utils/helpers/getTimestamp";
|
Reviver
|
||||||
|
} from "../../utils/JSONReviver";
|
||||||
|
import { getTimestamp } from "../../utils/helpers/getTimestamp";
|
||||||
|
|
||||||
export class RunningScript {
|
export class RunningScript {
|
||||||
// Initializes a RunningScript Object from a JSON save state
|
// Initializes a RunningScript Object from a JSON save state
|
||||||
@ -73,35 +74,6 @@ export class RunningScript {
|
|||||||
this.ramUsage = script.ramUsage;
|
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 {
|
log(txt: string): void {
|
||||||
if (this.logs.length > Settings.MaxLogCapacity) {
|
if (this.logs.length > Settings.MaxLogCapacity) {
|
||||||
//Delete first element and add new log entry to the end.
|
//Delete first element and add new log entry to the end.
|
||||||
|
20
src/Script/RunningScriptHelpers.ts
Normal file
20
src/Script/RunningScriptHelpers.ts
Normal file
@ -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
|
// This does NOT represent a script that is actively running and
|
||||||
// being evaluated. See RunningScript for that
|
// being evaluated. See RunningScript for that
|
||||||
import { calculateRamUsage } from "./RamCalculations";
|
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 { setTimeoutRef } from "../utils/SetTimeoutRef";
|
||||||
import { Generic_fromJSON,
|
import {
|
||||||
Generic_toJSON,
|
Generic_fromJSON,
|
||||||
Reviver } from "../../utils/JSONReviver";
|
Generic_toJSON,
|
||||||
|
Reviver
|
||||||
|
} from "../../utils/JSONReviver";
|
||||||
import { roundToTwo } from "../../utils/helpers/roundToTwo";
|
import { roundToTwo } from "../../utils/helpers/roundToTwo";
|
||||||
|
|
||||||
export class Script {
|
export class Script {
|
||||||
@ -64,7 +64,7 @@ export class Script {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save a script FROM THE SCRIPT EDITOR
|
// Save a script FROM THE SCRIPT EDITOR
|
||||||
saveScript(code: string, p: IPlayer): void {
|
saveScript(code: string, currServ: string): void {
|
||||||
if (routing.isOn(Page.ScriptEditor)) {
|
if (routing.isOn(Page.ScriptEditor)) {
|
||||||
//Update code and filename
|
//Update code and filename
|
||||||
this.code = code.replace(/^\s+|\s+$/g, '');
|
this.code = code.replace(/^\s+|\s+$/g, '');
|
||||||
@ -77,7 +77,7 @@ export class Script {
|
|||||||
this.filename = filenameElem!.value;
|
this.filename = filenameElem!.value;
|
||||||
|
|
||||||
// Server
|
// Server
|
||||||
this.server = p.currentServer;
|
this.server = currServ;
|
||||||
|
|
||||||
//Calculate/update ram usage, execution time, etc.
|
//Calculate/update ram usage, execution time, etc.
|
||||||
this.updateRamUsage();
|
this.updateRamUsage();
|
||||||
|
@ -1,33 +1,38 @@
|
|||||||
import { Script } from "./Script";
|
import { Script } from "./Script";
|
||||||
|
|
||||||
import { calculateRamUsage } from "./RamCalculations";
|
import { calculateRamUsage } from "./RamCalculations";
|
||||||
import { isScriptFilename } from "./ScriptHelpersTS";
|
import { isScriptFilename } from "./ScriptHelpersTS";
|
||||||
|
|
||||||
import {CONSTANTS} from "../Constants";
|
import {CONSTANTS} from "../Constants";
|
||||||
import {Engine} from "../engine";
|
import {Engine} from "../engine";
|
||||||
import { parseFconfSettings } from "../Fconf/Fconf";
|
import { parseFconfSettings } from "../Fconf/Fconf";
|
||||||
import { FconfSettings } from "../Fconf/FconfSettings";
|
import { FconfSettings } from "../Fconf/FconfSettings";
|
||||||
import {iTutorialSteps, iTutorialNextStep,
|
import {
|
||||||
ITutorial} from "../InteractiveTutorial";
|
iTutorialSteps,
|
||||||
import { addWorkerScript } from "../NetscriptWorker";
|
iTutorialNextStep,
|
||||||
import { Player } from "../Player";
|
ITutorial
|
||||||
import { AceEditor } from "../ScriptEditor/Ace";
|
} from "../InteractiveTutorial";
|
||||||
import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror";
|
import { Player } from "../Player";
|
||||||
import { AllServers } from "../Server/AllServers";
|
import { AceEditor } from "../ScriptEditor/Ace";
|
||||||
import { processSingleServerGrowth } from "../Server/ServerHelpers";
|
import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror";
|
||||||
import { Settings } from "../Settings/Settings";
|
import { AllServers } from "../Server/AllServers";
|
||||||
import { EditorSetting } from "../Settings/SettingEnums";
|
import { processSingleServerGrowth } from "../Server/ServerHelpers";
|
||||||
import { isValidFilePath } from "../Terminal/DirectoryHelpers";
|
import { Settings } from "../Settings/Settings";
|
||||||
import {TextFile} from "../TextFile";
|
import { EditorSetting } from "../Settings/SettingEnums";
|
||||||
|
import { isValidFilePath } from "../Terminal/DirectoryHelpers";
|
||||||
|
import { TextFile } from "../TextFile";
|
||||||
|
|
||||||
import {Page, routing} from "../ui/navigationTracking";
|
import { Page, routing } from "../ui/navigationTracking";
|
||||||
import {numeralWrapper} from "../ui/numeralFormat";
|
import { numeralWrapper } from "../ui/numeralFormat";
|
||||||
|
|
||||||
import {dialogBoxCreate} from "../../utils/DialogBox";
|
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||||
import {Reviver, Generic_toJSON,
|
import {
|
||||||
Generic_fromJSON} from "../../utils/JSONReviver";
|
Reviver,
|
||||||
import {compareArrays} from "../../utils/helpers/compareArrays";
|
Generic_toJSON,
|
||||||
import {createElement} from "../../utils/uiHelpers/createElement";
|
Generic_fromJSON
|
||||||
|
} from "../../utils/JSONReviver";
|
||||||
|
import { compareArrays } from "../../utils/helpers/compareArrays";
|
||||||
|
import { createElement } from "../../utils/uiHelpers/createElement";
|
||||||
|
|
||||||
var scriptEditorRamCheck = null, scriptEditorRamText = null;
|
var scriptEditorRamCheck = null, scriptEditorRamText = null;
|
||||||
export function scriptEditorInit() {
|
export function scriptEditorInit() {
|
||||||
@ -127,20 +132,22 @@ export function scriptEditorInit() {
|
|||||||
editorSelector.onchange = () => {
|
editorSelector.onchange = () => {
|
||||||
const opt = editorSelector.value;
|
const opt = editorSelector.value;
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case EditorSetting.Ace:
|
case EditorSetting.Ace: {
|
||||||
const codeMirrorCode = CodeMirrorEditor.getCode();
|
const codeMirrorCode = CodeMirrorEditor.getCode();
|
||||||
const codeMirrorFn = CodeMirrorEditor.getFilename();
|
const codeMirrorFn = CodeMirrorEditor.getFilename();
|
||||||
AceEditor.create();
|
AceEditor.create();
|
||||||
CodeMirrorEditor.setInvisible();
|
CodeMirrorEditor.setInvisible();
|
||||||
AceEditor.openScript(codeMirrorFn, codeMirrorCode);
|
AceEditor.openScript(codeMirrorFn, codeMirrorCode);
|
||||||
break;
|
break;
|
||||||
case EditorSetting.CodeMirror:
|
}
|
||||||
|
case EditorSetting.CodeMirror: {
|
||||||
const aceCode = AceEditor.getCode();
|
const aceCode = AceEditor.getCode();
|
||||||
const aceFn = AceEditor.getFilename();
|
const aceFn = AceEditor.getFilename();
|
||||||
CodeMirrorEditor.create();
|
CodeMirrorEditor.create();
|
||||||
AceEditor.setInvisible();
|
AceEditor.setInvisible();
|
||||||
CodeMirrorEditor.openScript(aceFn, aceCode);
|
CodeMirrorEditor.openScript(aceFn, aceCode);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
console.error(`Unrecognized Editor Setting: ${opt}`);
|
console.error(`Unrecognized Editor Setting: ${opt}`);
|
||||||
return;
|
return;
|
||||||
@ -229,7 +236,7 @@ function saveAndCloseScriptEditor() {
|
|||||||
let s = Player.getCurrentServer();
|
let s = Player.getCurrentServer();
|
||||||
for (var i = 0; i < s.scripts.length; i++) {
|
for (var i = 0; i < s.scripts.length; i++) {
|
||||||
if (filename == s.scripts[i].filename) {
|
if (filename == s.scripts[i].filename) {
|
||||||
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player);
|
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer);
|
||||||
Engine.loadTerminalContent();
|
Engine.loadTerminalContent();
|
||||||
return iTutorialNextStep();
|
return iTutorialNextStep();
|
||||||
}
|
}
|
||||||
@ -237,7 +244,7 @@ function saveAndCloseScriptEditor() {
|
|||||||
|
|
||||||
//If the current script does NOT exist, create a new one
|
//If the current script does NOT exist, create a new one
|
||||||
let script = new Script();
|
let script = new Script();
|
||||||
script.saveScript(getCurrentEditor().getCode(), Player);
|
script.saveScript(getCurrentEditor().getCode(), Player.currentServer);
|
||||||
s.scripts.push(script);
|
s.scripts.push(script);
|
||||||
|
|
||||||
return iTutorialNextStep();
|
return iTutorialNextStep();
|
||||||
@ -265,7 +272,7 @@ function saveAndCloseScriptEditor() {
|
|||||||
//If the current script already exists on the server, overwrite it
|
//If the current script already exists on the server, overwrite it
|
||||||
for (var i = 0; i < s.scripts.length; i++) {
|
for (var i = 0; i < s.scripts.length; i++) {
|
||||||
if (filename == s.scripts[i].filename) {
|
if (filename == s.scripts[i].filename) {
|
||||||
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player);
|
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer);
|
||||||
Engine.loadTerminalContent();
|
Engine.loadTerminalContent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -273,7 +280,7 @@ function saveAndCloseScriptEditor() {
|
|||||||
|
|
||||||
//If the current script does NOT exist, create a new one
|
//If the current script does NOT exist, create a new one
|
||||||
const script = new Script();
|
const script = new Script();
|
||||||
script.saveScript(getCurrentEditor().getCode(), Player);
|
script.saveScript(getCurrentEditor().getCode(), Player.currentServer);
|
||||||
s.scripts.push(script);
|
s.scripts.push(script);
|
||||||
} else if (filename.endsWith(".txt")) {
|
} else if (filename.endsWith(".txt")) {
|
||||||
for (var i = 0; i < s.textFiles.length; ++i) {
|
for (var i = 0; i < s.textFiles.length; ++i) {
|
||||||
@ -293,42 +300,7 @@ function saveAndCloseScriptEditor() {
|
|||||||
Engine.loadTerminalContent();
|
Engine.loadTerminalContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Called when the game is loaded. Loads all running scripts (from all servers)
|
export function scriptCalculateOfflineProduction(runningScriptObj) {
|
||||||
//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) {
|
|
||||||
//The Player object stores the last update time from when we were online
|
//The Player object stores the last update time from when we were online
|
||||||
var thisUpdate = new Date().getTime();
|
var thisUpdate = new Date().getTime();
|
||||||
var lastUpdate = Player.lastUpdate;
|
var lastUpdate = Player.lastUpdate;
|
||||||
|
@ -5,25 +5,42 @@ import { serverMetadata } from "./data/servers";
|
|||||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||||
|
|
||||||
import { IMap } from "../types";
|
import { IMap } from "../types";
|
||||||
import { createRandomIp,
|
import { createRandomIp } from "../../utils/IPAddress";
|
||||||
ipExists } from "../../utils/IPAddress";
|
|
||||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||||
import { Reviver } from "../../utils/JSONReviver";
|
import { Reviver } from "../../utils/JSONReviver";
|
||||||
|
|
||||||
// Map of all Servers that exist in the game
|
/**
|
||||||
// Key (string) = IP
|
* Map of all Servers that exist in the game
|
||||||
// Value = Server object
|
* Key (string) = IP
|
||||||
|
* Value = Server object
|
||||||
|
*/
|
||||||
export let AllServers: IMap<Server | HacknetServer> = {};
|
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
|
// Saftely add a Server to the AllServers map
|
||||||
export function AddToAllServers(server: Server | HacknetServer): void {
|
export function AddToAllServers(server: Server | HacknetServer): void {
|
||||||
var serverIp = server.ip;
|
var serverIp = server.ip;
|
||||||
if (ipExists(serverIp)) {
|
if (ipExists(serverIp)) {
|
||||||
console.log("IP of server that's being added: " + serverIp);
|
console.warn(`IP of server that's being added: ${serverIp}`);
|
||||||
console.log("Hostname of the server thats being added: " + server.hostname);
|
console.warn(`Hostname of the server thats being added: ${server.hostname}`);
|
||||||
console.log("The server that already has this IP is: " + AllServers[serverIp].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");
|
throw new Error("Error: Trying to add a server with an existing IP");
|
||||||
}
|
}
|
||||||
|
|
||||||
AllServers[serverIp] = server;
|
AllServers[serverIp] = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +88,7 @@ export function initForeignServers(homeComputer: Server) {
|
|||||||
for (const metadata of serverMetadata) {
|
for (const metadata of serverMetadata) {
|
||||||
const serverParams: IServerParams = {
|
const serverParams: IServerParams = {
|
||||||
hostname: metadata.hostname,
|
hostname: metadata.hostname,
|
||||||
ip: createRandomIp(),
|
ip: createUniqueRandomIp(),
|
||||||
numOpenPortsRequired: metadata.numOpenPortsRequired,
|
numOpenPortsRequired: metadata.numOpenPortsRequired,
|
||||||
organizationName: metadata.organizationName
|
organizationName: metadata.organizationName
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
// Class representing a single hackable Server
|
// Class representing a single hackable Server
|
||||||
import { BaseServer } from "./BaseServer";
|
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 { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||||
|
|
||||||
import { createRandomString } from "../utils/helpers/createRandomString";
|
import { createRandomString } from "../utils/helpers/createRandomString";
|
||||||
@ -12,7 +9,7 @@ import { Generic_fromJSON,
|
|||||||
Generic_toJSON,
|
Generic_toJSON,
|
||||||
Reviver } from "../../utils/JSONReviver";
|
Reviver } from "../../utils/JSONReviver";
|
||||||
|
|
||||||
interface IConstructorParams {
|
export interface IConstructorParams {
|
||||||
adminRights?: boolean;
|
adminRights?: boolean;
|
||||||
hackDifficulty?: number;
|
hackDifficulty?: number;
|
||||||
hostname: string;
|
hostname: string;
|
||||||
@ -77,17 +74,6 @@ export class Server extends BaseServer {
|
|||||||
this.hostname = createRandomString(10);
|
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;
|
this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false;
|
||||||
|
|
||||||
//RAM, CPU speed and Scripts
|
//RAM, CPU speed and Scripts
|
||||||
|
@ -1,13 +1,39 @@
|
|||||||
import { AllServers } from "./AllServers";
|
import {
|
||||||
import { Server } from "./Server";
|
AllServers,
|
||||||
|
createUniqueRandomIp,
|
||||||
|
ipExists,
|
||||||
|
} from "./AllServers";
|
||||||
|
import { Server, IConstructorParams } from "./Server";
|
||||||
|
|
||||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||||
import { CONSTANTS } from "../Constants";
|
import { CONSTANTS } from "../Constants";
|
||||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
import { Programs } from "../Programs/Programs";
|
import { Programs } from "../Programs/Programs";
|
||||||
|
|
||||||
import {isValidIPAddress} from "../../utils/helpers/isValidIPAddress";
|
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
|
// Returns the number of cycles needed to grow the specified server by the
|
||||||
// specified amount. 'growth' parameter is in decimal form, not percentage
|
// specified amount. 'growth' parameter is in decimal form, not percentage
|
||||||
|
@ -2,15 +2,19 @@
|
|||||||
* Implements functions for purchasing servers or purchasing more RAM for
|
* Implements functions for purchasing servers or purchasing more RAM for
|
||||||
* the home computer
|
* the home computer
|
||||||
*/
|
*/
|
||||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
import {
|
||||||
import { CONSTANTS } from "../Constants";
|
AddToAllServers,
|
||||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
createUniqueRandomIp,
|
||||||
import { AddToAllServers } from "../Server/AllServers";
|
} from "./AllServers";
|
||||||
import { Server } from "../Server/Server";
|
import { safetlyCreateUniqueServer } from "./ServerHelpers";
|
||||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
|
||||||
import { createRandomIp } from "../../utils/IPAddress";
|
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||||
import { yesNoTxtInpBoxGetInput } from "../../utils/YesNoBox";
|
import { CONSTANTS } from "../Constants";
|
||||||
import { isPowerOfTwo } from "../../utils/helpers/isPowerOfTwo";
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
|
|
||||||
|
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||||
|
import { yesNoTxtInpBoxGetInput } from "../../utils/YesNoBox";
|
||||||
|
import { isPowerOfTwo } from "../../utils/helpers/isPowerOfTwo";
|
||||||
|
|
||||||
// Returns the cost of purchasing a server with the given RAM
|
// Returns the cost of purchasing a server with the given RAM
|
||||||
// Returns Infinity for invalid 'ram' arguments
|
// Returns Infinity for invalid 'ram' arguments
|
||||||
@ -66,17 +70,22 @@ export function purchaseServer(ram: number, p: IPlayer) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create server
|
// Create server
|
||||||
var newServ = new Server({
|
const newServ = safetlyCreateUniqueServer({
|
||||||
ip:createRandomIp(), hostname:hostname, organizationName:"",
|
adminRights: true,
|
||||||
isConnectedTo:false, adminRights:true, purchasedByPlayer:true, maxRam:ram
|
hostname: hostname,
|
||||||
|
ip: createUniqueRandomIp(),
|
||||||
|
isConnectedTo: false,
|
||||||
|
maxRam:ram,
|
||||||
|
organizationName: "",
|
||||||
|
purchasedByPlayer: true,
|
||||||
});
|
});
|
||||||
AddToAllServers(newServ);
|
AddToAllServers(newServ);
|
||||||
|
|
||||||
//Add to Player's purchasedServers array
|
// Add to Player's purchasedServers array
|
||||||
p.purchasedServers.push(newServ.ip);
|
p.purchasedServers.push(newServ.ip);
|
||||||
|
|
||||||
//Connect new server to home computer
|
// Connect new server to home computer
|
||||||
var homeComputer = p.getHomeComputer();
|
var homeComputer = p.getHomeComputer();
|
||||||
homeComputer.serversOnNetwork.push(newServ.ip);
|
homeComputer.serversOnNetwork.push(newServ.ip);
|
||||||
newServ.serversOnNetwork.push(homeComputer.ip);
|
newServ.serversOnNetwork.push(homeComputer.ip);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import { CONSTANTS } from "../Constants";
|
import { CONSTANTS } from "../Constants";
|
||||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
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) {
|
export function updateSourceFileFlags(p: IPlayer) {
|
||||||
for (let i = 0; i < SourceFileFlags.length; ++i) {
|
for (let i = 0; i < SourceFileFlags.length; ++i) {
|
||||||
|
296
src/StockMarket/BuyingAndSelling.ts
Normal file
296
src/StockMarket/BuyingAndSelling.ts
Normal file
@ -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 pos: PositionTypes;
|
||||||
readonly price: number;
|
readonly price: number;
|
||||||
readonly shares: number;
|
shares: number;
|
||||||
readonly stock: Stock;
|
readonly stock: Stock;
|
||||||
readonly type: OrderTypes;
|
readonly type: OrderTypes;
|
||||||
|
|
||||||
constructor(stk: Stock = new Stock(), shares: number=0, price: number=0, typ: OrderTypes=OrderTypes.LimitBuy, pos: PositionTypes=PositionTypes.Long) {
|
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.stock = stk;
|
||||||
this.shares = shares;
|
this.shares = shares;
|
||||||
this.price = price;
|
this.price = price;
|
||||||
|
252
src/StockMarket/OrderProcessing.ts
Normal file
252
src/StockMarket/OrderProcessing.ts
Normal file
@ -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 { Order } from "./Order";
|
||||||
|
import { processOrders } from "./OrderProcessing";
|
||||||
import { Stock } from "./Stock";
|
import { Stock } from "./Stock";
|
||||||
import {
|
import {
|
||||||
getBuyTransactionCost,
|
getBuyTransactionCost,
|
||||||
@ -17,7 +24,7 @@ import { StockSymbols } from "./data/StockSymbols";
|
|||||||
import { StockMarketRoot } from "./ui/Root";
|
import { StockMarketRoot } from "./ui/Root";
|
||||||
|
|
||||||
import { CONSTANTS } from "../Constants";
|
import { CONSTANTS } from "../Constants";
|
||||||
import { WorkerScript } from "../NetscriptWorker";
|
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||||
import { Player } from "../Player";
|
import { Player } from "../Player";
|
||||||
|
|
||||||
import { Page, routing } from ".././ui/navigationTracking";
|
import { Page, routing } from ".././ui/navigationTracking";
|
||||||
@ -29,10 +36,12 @@ import { Reviver } from "../../utils/JSONReviver";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom";
|
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) {
|
export function placeOrder(stock, shares, price, type, position, workerScript=null) {
|
||||||
var tixApi = (workerScript instanceof WorkerScript);
|
const tixApi = (workerScript instanceof WorkerScript);
|
||||||
var order = new Order(stock, shares, price, type, position);
|
if (typeof shares !== "number" || typeof price !== "number") {
|
||||||
if (isNaN(shares) || isNaN(price)) {
|
|
||||||
if (tixApi) {
|
if (tixApi) {
|
||||||
workerScript.scriptRef.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
|
workerScript.scriptRef.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
|
||||||
} else {
|
} else {
|
||||||
@ -40,21 +49,27 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const order = new Order(stock, shares, price, type, position);
|
||||||
if (StockMarket["Orders"] == null) {
|
if (StockMarket["Orders"] == null) {
|
||||||
const orders = {};
|
const orders = {};
|
||||||
for (const name in StockMarket) {
|
for (const name in StockMarket) {
|
||||||
if (StockMarket.hasOwnProperty(name)) {
|
const stk = StockMarket[name];
|
||||||
const stk = StockMarket[name];
|
if (!(stk instanceof Stock)) { continue; }
|
||||||
if (!(stk instanceof Stock)) { continue; }
|
orders[stk.symbol] = [];
|
||||||
orders[stk.symbol] = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
StockMarket["Orders"] = orders;
|
StockMarket["Orders"] = orders;
|
||||||
}
|
}
|
||||||
StockMarket["Orders"][stock.symbol].push(order);
|
StockMarket["Orders"][stock.symbol].push(order);
|
||||||
//Process to see if it should be executed immediately
|
|
||||||
processOrders(order.stock, order.type, order.pos);
|
// Process to see if it should be executed immediately
|
||||||
|
const processOrderRefs = {
|
||||||
|
rerenderFn: displayStockMarketContent,
|
||||||
|
stockMarket: StockMarket,
|
||||||
|
}
|
||||||
|
processOrders(order.stock, order.type, order.pos, processOrderRefs);
|
||||||
displayStockMarketContent();
|
displayStockMarketContent();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +94,7 @@ export function cancelOrder(params, workerScript=null) {
|
|||||||
// Order properties are passed in. Need to look for the order
|
// Order properties are passed in. Need to look for the order
|
||||||
var stockOrders = StockMarket["Orders"][params.stock.symbol];
|
var stockOrders = StockMarket["Orders"][params.stock.symbol];
|
||||||
var orderTxt = params.stock.symbol + " - " + params.shares + " @ " +
|
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) {
|
for (var i = 0; i < stockOrders.length; ++i) {
|
||||||
var order = stockOrders[i];
|
var order = stockOrders[i];
|
||||||
if (params.shares === order.shares &&
|
if (params.shares === order.shares &&
|
||||||
@ -102,50 +117,6 @@ export function cancelOrder(params, workerScript=null) {
|
|||||||
return false;
|
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) {
|
export function loadStockMarket(saveString) {
|
||||||
if (saveString === "") {
|
if (saveString === "") {
|
||||||
StockMarket = {};
|
StockMarket = {};
|
||||||
@ -172,11 +143,9 @@ export function initStockMarket() {
|
|||||||
|
|
||||||
const orders = {};
|
const orders = {};
|
||||||
for (const name in StockMarket) {
|
for (const name in StockMarket) {
|
||||||
if (StockMarket.hasOwnProperty(name)) {
|
const stock = StockMarket[name];
|
||||||
const stock = StockMarket[name];
|
if (!(stock instanceof Stock)) { continue; }
|
||||||
if (!(stock instanceof Stock)) { continue; }
|
orders[stock.symbol] = [];
|
||||||
orders[stock.symbol] = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
StockMarket["Orders"] = orders;
|
StockMarket["Orders"] = orders;
|
||||||
|
|
||||||
@ -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
|
// Stock prices updated every 6 seconds
|
||||||
const msPerStockUpdate = 6e3;
|
const msPerStockUpdate = 6e3;
|
||||||
const cyclesPerStockUpdate = msPerStockUpdate / CONSTANTS.MilliPerCycle;
|
const cyclesPerStockUpdate = msPerStockUpdate / CONSTANTS.MilliPerCycle;
|
||||||
@ -494,18 +217,22 @@ export function processStockPrices(numCycles=1) {
|
|||||||
if (isNaN(chc)) { chc = 0.5; }
|
if (isNaN(chc)) { chc = 0.5; }
|
||||||
|
|
||||||
const c = Math.random();
|
const c = Math.random();
|
||||||
|
const processOrderRefs = {
|
||||||
|
rerenderFn: displayStockMarketContent,
|
||||||
|
stockMarket: StockMarket,
|
||||||
|
}
|
||||||
if (c < chc) {
|
if (c < chc) {
|
||||||
stock.price *= (1 + av);
|
stock.price *= (1 + av);
|
||||||
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short);
|
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short, processOrderRefs);
|
||||||
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long);
|
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long, processOrderRefs);
|
||||||
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long);
|
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long, processOrderRefs);
|
||||||
processOrders(stock, OrderTypes.StopSell, PositionTypes.Short);
|
processOrders(stock, OrderTypes.StopSell, PositionTypes.Short, processOrderRefs);
|
||||||
} else {
|
} else {
|
||||||
stock.price /= (1 + av);
|
stock.price /= (1 + av);
|
||||||
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long);
|
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long, processOrderRefs);
|
||||||
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short);
|
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short, processOrderRefs);
|
||||||
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short);
|
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short, processOrderRefs);
|
||||||
processOrders(stock, OrderTypes.StopSell, PositionTypes.Long);
|
processOrders(stock, OrderTypes.StopSell, PositionTypes.Long, processOrderRefs);
|
||||||
}
|
}
|
||||||
|
|
||||||
let otlkMagChange = stock.otlkMag * av;
|
let otlkMagChange = stock.otlkMag * av;
|
||||||
@ -530,67 +257,6 @@ export function processStockPrices(numCycles=1) {
|
|||||||
displayStockMarketContent();
|
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;
|
let stockMarketContainer = null;
|
||||||
function setStockMarketContainer() {
|
function setStockMarketContainer() {
|
||||||
stockMarketContainer = document.getElementById("stock-market-container");
|
stockMarketContainer = document.getElementById("stock-market-container");
|
||||||
@ -599,7 +265,6 @@ function setStockMarketContainer() {
|
|||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", setStockMarketContainer);
|
document.addEventListener("DOMContentLoaded", setStockMarketContainer);
|
||||||
|
|
||||||
|
|
||||||
function initStockMarketFnForReact() {
|
function initStockMarketFnForReact() {
|
||||||
initStockMarket();
|
initStockMarket();
|
||||||
initSymbolToStockMap();
|
initSymbolToStockMap();
|
||||||
|
@ -3,7 +3,7 @@ import { PositionTypes } from "./data/PositionTypes";
|
|||||||
import { CONSTANTS } from "../Constants";
|
import { CONSTANTS } from "../Constants";
|
||||||
|
|
||||||
// Amount by which a stock's forecast changes during each price movement
|
// 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
|
* 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);
|
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;
|
let currPrice = stock.price;
|
||||||
function processPriceMovement() {
|
function processPriceMovement() {
|
||||||
if (isLong) {
|
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) {
|
for (let i = 1; i < numIterations; ++i) {
|
||||||
processPriceMovement();
|
processPriceMovement();
|
||||||
}
|
}
|
||||||
|
|
||||||
stock.price = currPrice;
|
|
||||||
stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement);
|
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
|
// Forecast always decreases in magnitude
|
||||||
const forecastChange = Math.min(5, forecastChangePerPriceMovement * numIterations);
|
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
|
||||||
stock.otlkMag -= forecastChange;
|
stock.otlkMag -= forecastChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,18 +227,8 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
|
|||||||
|
|
||||||
const isLong = (posType === PositionTypes.Long);
|
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;
|
let currPrice = stock.price;
|
||||||
for (let i = 1; i < numIterations; ++i) {
|
function processPriceMovement() {
|
||||||
// Price movement
|
|
||||||
if (isLong) {
|
if (isLong) {
|
||||||
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
currPrice *= calculateDecreasingPriceMovement(stock)!;
|
||||||
} else {
|
} else {
|
||||||
@ -233,11 +236,37 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stock.price = currPrice;
|
// 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);
|
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
|
// Forecast always decreases in magnitude
|
||||||
const forecastChange = Math.min(5, forecastChangePerPriceMovement * numIterations);
|
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
|
||||||
stock.otlkMag -= forecastChange;
|
stock.otlkMag -= forecastChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
import { OrderTypes } from "../data/OrderTypes";
|
import { OrderTypes } from "../data/OrderTypes";
|
||||||
import { PositionTypes } from "../data/PositionTypes";
|
import { PositionTypes } from "../data/PositionTypes";
|
||||||
|
|
||||||
import { CONSTANTS } from "../../Constants";
|
|
||||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
@ -58,6 +57,7 @@ type IState = {
|
|||||||
orderType: SelectorOrderType;
|
orderType: SelectorOrderType;
|
||||||
position: PositionTypes;
|
position: PositionTypes;
|
||||||
qty: string;
|
qty: string;
|
||||||
|
rerenderFlag: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StockTicker extends React.Component<IProps, IState> {
|
export class StockTicker extends React.Component<IProps, IState> {
|
||||||
@ -68,6 +68,7 @@ export class StockTicker extends React.Component<IProps, IState> {
|
|||||||
orderType: SelectorOrderType.Market,
|
orderType: SelectorOrderType.Market,
|
||||||
position: PositionTypes.Long,
|
position: PositionTypes.Long,
|
||||||
qty: "",
|
qty: "",
|
||||||
|
rerenderFlag: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getBuyTransactionCostText = this.getBuyTransactionCostText.bind(this);
|
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.handleQuantityChange = this.handleQuantityChange.bind(this);
|
||||||
this.handleSellButtonClick = this.handleSellButtonClick.bind(this);
|
this.handleSellButtonClick = this.handleSellButtonClick.bind(this);
|
||||||
this.handleSellAllButtonClick = this.handleSellAllButtonClick.bind(this);
|
this.handleSellAllButtonClick = this.handleSellAllButtonClick.bind(this);
|
||||||
|
this.rerender = this.rerender.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
createPlaceOrderPopupBox(yesTxt: string, popupTxt: string, yesBtnCb: (price: number) => void) {
|
createPlaceOrderPopupBox(yesTxt: string, popupTxt: string, yesBtnCb: (price: number) => void) {
|
||||||
@ -172,6 +174,7 @@ export class StockTicker extends React.Component<IProps, IState> {
|
|||||||
} else {
|
} else {
|
||||||
this.props.buyStockLong(this.props.stock, shares);
|
this.props.buyStockLong(this.props.stock, shares);
|
||||||
}
|
}
|
||||||
|
this.rerender();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SelectorOrderType.Limit: {
|
case SelectorOrderType.Limit: {
|
||||||
@ -213,30 +216,13 @@ export class StockTicker extends React.Component<IProps, IState> {
|
|||||||
} else {
|
} else {
|
||||||
this.props.buyStockLong(stock, maxShares);
|
this.props.buyStockLong(stock, maxShares);
|
||||||
}
|
}
|
||||||
|
this.rerender();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SelectorOrderType.Limit: {
|
default: {
|
||||||
this.createPlaceOrderPopupBox(
|
dialogBoxCreate(`ERROR: 'Buy Max' only works for Market Orders`);
|
||||||
"Place Buy Limit Order",
|
|
||||||
"Enter the price for your Limit Order",
|
|
||||||
(price: number) => {
|
|
||||||
this.props.placeOrder(stock, maxShares, price, OrderTypes.LimitBuy, this.state.position);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
break;
|
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 {
|
} else {
|
||||||
this.props.sellStockLong(this.props.stock, shares);
|
this.props.sellStockLong(this.props.stock, shares);
|
||||||
}
|
}
|
||||||
|
this.rerender();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SelectorOrderType.Limit: {
|
case SelectorOrderType.Limit: {
|
||||||
@ -348,6 +335,7 @@ export class StockTicker extends React.Component<IProps, IState> {
|
|||||||
} else {
|
} else {
|
||||||
this.props.sellStockLong(stock, stock.playerShares);
|
this.props.sellStockLong(stock, stock.playerShares);
|
||||||
}
|
}
|
||||||
|
this.rerender();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -367,6 +355,14 @@ export class StockTicker extends React.Component<IProps, IState> {
|
|||||||
return (this.props.p.bitNodeN === 8 || (SourceFileFlags[8] >= 2));
|
return (this.props.p.bitNodeN === 8 || (SourceFileFlags[8] >= 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rerender(): void {
|
||||||
|
this.setState((prevState) => {
|
||||||
|
return {
|
||||||
|
rerenderFlag: !prevState.rerenderFlag,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// Determine if the player's intended transaction will cause a price movement
|
// Determine if the player's intended transaction will cause a price movement
|
||||||
let causesMovement: boolean = false;
|
let causesMovement: boolean = false;
|
||||||
|
@ -18,7 +18,6 @@ type IProps = {
|
|||||||
|
|
||||||
export function StockTickerHeaderText(props: IProps): React.ReactElement {
|
export function StockTickerHeaderText(props: IProps): React.ReactElement {
|
||||||
const stock = props.stock;
|
const stock = props.stock;
|
||||||
const p = props.p;
|
|
||||||
|
|
||||||
const stockPriceFormat = numeralWrapper.formatMoney(stock.price);
|
const stockPriceFormat = numeralWrapper.formatMoney(stock.price);
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ export class StockTickerOrder extends React.Component<IProps, any> {
|
|||||||
const order = this.props.order;
|
const order = this.props.order;
|
||||||
|
|
||||||
const posTxt = order.pos === PositionTypes.Long ? "Long Position" : "Short Position";
|
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 (
|
return (
|
||||||
<li>
|
<li>
|
||||||
|
@ -57,6 +57,7 @@ import { killWorkerScript, addWorkerScript } from "./NetscriptWorker";
|
|||||||
import { Player } from "./Player";
|
import { Player } from "./Player";
|
||||||
import { hackWorldDaemon } from "./RedPill";
|
import { hackWorldDaemon } from "./RedPill";
|
||||||
import { RunningScript } from "./Script/RunningScript";
|
import { RunningScript } from "./Script/RunningScript";
|
||||||
|
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
|
||||||
import { findRunningScript } from "./Script/ScriptHelpers";
|
import { findRunningScript } from "./Script/ScriptHelpers";
|
||||||
import { isScriptFilename } from "./Script/ScriptHelpersTS";
|
import { isScriptFilename } from "./Script/ScriptHelpersTS";
|
||||||
import { AllServers } from "./Server/AllServers";
|
import { AllServers } from "./Server/AllServers";
|
||||||
@ -1447,7 +1448,7 @@ let Terminal = {
|
|||||||
let spacesThread = Array(numSpacesThread+1).join(" ");
|
let spacesThread = Array(numSpacesThread+1).join(" ");
|
||||||
|
|
||||||
// Calculate and transform RAM usage
|
// 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];
|
var entry = [script.filename, spacesScript, script.threads, spacesThread, ramUsage];
|
||||||
post(entry.join(""));
|
post(entry.join(""));
|
||||||
@ -2268,7 +2269,7 @@ let Terminal = {
|
|||||||
|
|
||||||
// This has to come after addWorkerScript() because that fn
|
// This has to come after addWorkerScript() because that fn
|
||||||
// updates the RAM usage. This kinda sucks, address if possible
|
// updates the RAM usage. This kinda sucks, address if possible
|
||||||
server.runScript(runningScriptObj, Player);
|
server.runScript(runningScriptObj, Player.hacknet_node_money_mult);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,11 +49,10 @@ import { LocationRoot } from "./Locations/ui/Root";
|
|||||||
import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers";
|
import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers";
|
||||||
import { inMission, currMission } from "./Missions";
|
import { inMission, currMission } from "./Missions";
|
||||||
import {
|
import {
|
||||||
initSingularitySFFlags,
|
loadAllRunningScripts,
|
||||||
hasSingularitySF,
|
runScriptsLoop,
|
||||||
hasCorporationSF
|
updateOnlineScriptTimes,
|
||||||
} from "./NetscriptFunctions";
|
} from "./NetscriptWorker";
|
||||||
import { updateOnlineScriptTimes, runScriptsLoop } from "./NetscriptWorker";
|
|
||||||
import { Player } from "./Player";
|
import { Player } from "./Player";
|
||||||
import { prestigeAugmentation, prestigeSourceFile } from "./Prestige";
|
import { prestigeAugmentation, prestigeSourceFile } from "./Prestige";
|
||||||
import { Programs } from "./Programs/Programs";
|
import { Programs } from "./Programs/Programs";
|
||||||
@ -66,7 +65,6 @@ import { redPillFlag, hackWorldDaemon } from "./RedPill";
|
|||||||
import { saveObject, loadGame } from "./SaveObject";
|
import { saveObject, loadGame } from "./SaveObject";
|
||||||
import {
|
import {
|
||||||
getCurrentEditor,
|
getCurrentEditor,
|
||||||
loadAllRunningScripts,
|
|
||||||
scriptEditorInit,
|
scriptEditorInit,
|
||||||
updateScriptEditorContent
|
updateScriptEditorContent
|
||||||
} from "./Script/ScriptHelpers";
|
} from "./Script/ScriptHelpers";
|
||||||
@ -1087,7 +1085,6 @@ const Engine = {
|
|||||||
initSymbolToStockMap();
|
initSymbolToStockMap();
|
||||||
}
|
}
|
||||||
initLiterature();
|
initLiterature();
|
||||||
initSingularitySFFlags();
|
|
||||||
updateSourceFileFlags(Player);
|
updateSourceFileFlags(Player);
|
||||||
|
|
||||||
// Calculate the number of cycles have elapsed while offline
|
// Calculate the number of cycles have elapsed while offline
|
||||||
@ -1213,7 +1210,6 @@ const Engine = {
|
|||||||
initAugmentations();
|
initAugmentations();
|
||||||
initMessages();
|
initMessages();
|
||||||
initLiterature();
|
initLiterature();
|
||||||
initSingularitySFFlags();
|
|
||||||
|
|
||||||
// Open main menu accordions for new game
|
// Open main menu accordions for new game
|
||||||
const hackingHdr = document.getElementById("hacking-menu-header");
|
const hackingHdr = document.getElementById("hacking-menu-header");
|
||||||
|
@ -1,14 +1,28 @@
|
|||||||
import { CONSTANTS } from "../src/Constants";
|
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 {
|
import {
|
||||||
calculateIncreasingPriceMovement,
|
calculateIncreasingPriceMovement,
|
||||||
calculateDecreasingPriceMovement,
|
calculateDecreasingPriceMovement,
|
||||||
|
forecastChangePerPriceMovement,
|
||||||
getBuyTransactionCost,
|
getBuyTransactionCost,
|
||||||
getSellTransactionGain,
|
getSellTransactionGain,
|
||||||
processBuyTransactionPriceMovement,
|
processBuyTransactionPriceMovement,
|
||||||
processSellTransactionPriceMovement,
|
processSellTransactionPriceMovement,
|
||||||
} from "../src/StockMarket/StockMarketHelpers";
|
} 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 assert = chai.assert;
|
||||||
const expect = chai.expect;
|
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("Transaction Cost Calculator Functions", function() {
|
||||||
describe("getBuyTransactionCost()", function() {
|
describe("getBuyTransactionCost()", function() {
|
||||||
it("should fail on invalid 'stock' argument", function() {
|
it("should fail on invalid 'stock' argument", function() {
|
||||||
@ -279,6 +354,31 @@ describe("Stock Market Tests", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Price Movement Processor Functions", 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() {
|
describe("processBuyTransactionPriceMovement()", function() {
|
||||||
const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2);
|
const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2);
|
||||||
const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares;
|
const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares;
|
||||||
@ -305,53 +405,107 @@ describe("Stock Market Tests", function() {
|
|||||||
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
|
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 oldPrice = stock.price;
|
||||||
|
const oldForecast = stock.otlkMag;
|
||||||
|
|
||||||
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
|
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
|
||||||
expect(stock.price).to.equal(oldPrice);
|
expect(stock.price).to.equal(oldPrice);
|
||||||
|
expect(stock.otlkMag).to.equal(oldForecast);
|
||||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
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 oldPrice = stock.price;
|
||||||
|
const oldForecast = stock.otlkMag;
|
||||||
|
|
||||||
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
|
processBuyTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
|
||||||
expect(stock.price).to.equal(oldPrice);
|
expect(stock.price).to.equal(oldPrice);
|
||||||
|
expect(stock.otlkMag).to.equal(oldForecast);
|
||||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
||||||
const oldPrice = stock.price;
|
const oldPrice = stock.price;
|
||||||
function getNthPrice(n) {
|
const oldForecast = stock.otlkMag;
|
||||||
let price = oldPrice;
|
|
||||||
for (let i = 1; i < n; ++i) {
|
|
||||||
price *= calculateIncreasingPriceMovement(stock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return price;
|
|
||||||
}
|
|
||||||
|
|
||||||
processBuyTransactionPriceMovement(stock, mvmtShares, PositionTypes.Long);
|
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);
|
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
||||||
const oldPrice = stock.price;
|
const oldPrice = stock.price;
|
||||||
function getNthPrice(n) {
|
const oldForecast = stock.otlkMag;
|
||||||
let price = oldPrice;
|
|
||||||
for (let i = 1; i < n; ++i) {
|
|
||||||
price *= calculateDecreasingPriceMovement(stock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return price;
|
|
||||||
}
|
|
||||||
|
|
||||||
processBuyTransactionPriceMovement(stock, mvmtShares, PositionTypes.Short);
|
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);
|
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() {
|
describe("processSellTransactionPriceMovement()", function() {
|
||||||
@ -380,53 +534,137 @@ describe("Stock Market Tests", function() {
|
|||||||
expect(stock.shareTxUntilMovement).to.equal(oldTracker);
|
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 oldPrice = stock.price;
|
||||||
|
const oldForecast = stock.otlkMag;
|
||||||
|
|
||||||
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
|
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Long);
|
||||||
expect(stock.price).to.equal(oldPrice);
|
expect(stock.price).to.equal(oldPrice);
|
||||||
|
expect(stock.otlkMag).to.equal(oldForecast);
|
||||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
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 oldPrice = stock.price;
|
||||||
|
const oldForecast = stock.otlkMag;
|
||||||
|
|
||||||
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
|
processSellTransactionPriceMovement(stock, noMvmtShares, PositionTypes.Short);
|
||||||
expect(stock.price).to.equal(oldPrice);
|
expect(stock.price).to.equal(oldPrice);
|
||||||
|
expect(stock.otlkMag).to.equal(oldForecast);
|
||||||
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
it("should properly evaluate LONG transactions that trigger price movements", function() {
|
||||||
const oldPrice = stock.price;
|
const oldPrice = stock.price;
|
||||||
function getNthPrice(n) {
|
const oldForecast = stock.otlkMag;
|
||||||
let price = oldPrice;
|
|
||||||
for (let i = 1; i < n; ++i) {
|
|
||||||
price *= calculateDecreasingPriceMovement(stock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return price;
|
|
||||||
}
|
|
||||||
|
|
||||||
processSellTransactionPriceMovement(stock, mvmtShares, PositionTypes.Long);
|
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);
|
expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
it("should properly evaluate SHORT transactions that trigger price movements", function() {
|
||||||
const oldPrice = stock.price;
|
const oldPrice = stock.price;
|
||||||
function getNthPrice(n) {
|
const oldForecast = stock.otlkMag;
|
||||||
let price = oldPrice;
|
|
||||||
for (let i = 1; i < n; ++i) {
|
|
||||||
price *= calculateIncreasingPriceMovement(stock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return price;
|
|
||||||
}
|
|
||||||
|
|
||||||
processSellTransactionPriceMovement(stock, mvmtShares, PositionTypes.Short);
|
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);
|
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";
|
import { getRandomByte } from "./helpers/getRandomByte";
|
||||||
|
|
||||||
/* Functions to deal with manipulating IP addresses*/
|
/**
|
||||||
|
* Generate a random IP address
|
||||||
//Generate a random IP address
|
* Does not check to see if the IP already exists in the game
|
||||||
//Will not return an IP address that already exists in the AllServers array
|
*/
|
||||||
export function createRandomIp(): string {
|
export function createRandomIp(): string {
|
||||||
const ip: string = getRandomByte(99) + '.' +
|
const ip: string = getRandomByte(99) + '.' +
|
||||||
getRandomByte(9) + '.' +
|
getRandomByte(9) + '.' +
|
||||||
getRandomByte(9) + '.' +
|
getRandomByte(9) + '.' +
|
||||||
getRandomByte(9);
|
getRandomByte(9);
|
||||||
|
|
||||||
// If the Ip already exists, recurse to create a new one
|
|
||||||
if (ipExists(ip)) {
|
|
||||||
return createRandomIp();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip;
|
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);
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user