+ >
+ )
+}
diff --git a/src/Augmentation/ui/PurchasedAugmentations.tsx b/src/Augmentation/ui/PurchasedAugmentations.tsx
new file mode 100644
index 000000000..3bc54d837
--- /dev/null
+++ b/src/Augmentation/ui/PurchasedAugmentations.tsx
@@ -0,0 +1,32 @@
+/**
+ * React component for displaying all of the player's purchased (but not installed)
+ * Augmentations on the Augmentations UI.
+ */
+import * as React from "react";
+
+import { Augmentations } from "../../Augmentation/Augmentations";
+import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
+import { Player } from "../../Player";
+
+import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
+
+export function PurchasedAugmentations(): React.ReactElement {
+ const augs: React.ReactElement[] = [];
+ for (const ownedAug of Player.queuedAugmentations) {
+ const aug = Augmentations[ownedAug.name];
+ let level = null;
+ if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {
+ level = ownedAug.level;
+ }
+
+ augs.push(
+
+
+
+ )
+ }
+
+ return (
+
{augs}
+ )
+}
diff --git a/src/Augmentation/ui/Root.tsx b/src/Augmentation/ui/Root.tsx
new file mode 100644
index 000000000..10e733556
--- /dev/null
+++ b/src/Augmentation/ui/Root.tsx
@@ -0,0 +1,83 @@
+/**
+ * Root React component for the Augmentations UI page that display all of your
+ * owned and purchased Augmentations and Source-Files.
+ */
+import * as React from "react";
+
+import { InstalledAugmentationsAndSourceFiles } from "./InstalledAugmentationsAndSourceFiles";
+import { PlayerMultipliers } from "./PlayerMultipliers";
+import { PurchasedAugmentations } from "./PurchasedAugmentations";
+
+import { Player } from "../../Player";
+import { StdButton } from "../../ui/React/StdButton";
+
+type IProps = {
+ exportGameFn: () => void;
+ installAugmentationsFn: () => void;
+}
+
+type IState = {
+
+}
+
+export class AugmentationsRoot extends React.Component {
+ constructor(props: IProps) {
+ super(props);
+ }
+
+ render() {
+ return (
+
+
Purchased Augmentations
+
+ Below is a list of all Augmentations you have purchased but not
+ yet installed. Click the button below to install them.
+
+
+ WARNING: Installing your Augmentations resets most of your progress,
+ including:
+
+
- Stats/Skill levels and Experience
+
- Money
+
- Scripts on every computer but your home computer
+
- Purchased servers
+
- Hacknet Nodes
+
- Faction/Company reputation
+
- Stocks
+
+ Installing Augmentations lets you start over with the perks and
+ benefits granted by all of the Augmentations you have ever
+ installed. Also, you will keep any scripts and RAM/Core upgrades
+ on your home computer (but you will lose all programs besides
+ NUKE.exe)
+
+
+
+
+
+
+
+
Installed Augmentations
+
+ {
+ `List of all Augmentations ${Player.sourceFiles.length > 0 ? "and Source Files " : ""} ` +
+ `that have been installed. You have gained the effects of these.`
+ }
+
+
+
+
+
+
+ )
+ }
+}
diff --git a/src/BitNode/BitNode.ts b/src/BitNode/BitNode.ts
index a4803866a..7f443c14a 100644
--- a/src/BitNode/BitNode.ts
+++ b/src/BitNode/BitNode.ts
@@ -25,235 +25,232 @@ class BitNode {
}
-export let BitNodes: IMap = {};
+export const BitNodes: IMap = {};
-export function initBitNodes() {
- BitNodes = {};
- BitNodes["BitNode1"] = new BitNode(1, "Source Genesis", "The original BitNode",
- "The first BitNode created by the Enders to imprison the minds of humans. It became " +
- "the prototype and testing-grounds for all of the BitNodes that followed.
" +
- "This is the first BitNode that you play through. It has no special " +
- "modifications or mechanics.
" +
- "Destroying this BitNode will give you Source-File 1, or if you already have " +
- "this Source-File it will upgrade its level up to a maximum of 3. This Source-File " +
- "lets the player start with 32GB of RAM on his/her home computer when entering a " +
- "new BitNode, and also increases all of the player's multipliers by:
" +
- "Level 1: 16% " +
- "Level 2: 24% " +
- "Level 3: 28%");
- BitNodes["BitNode2"] = new BitNode(2, "Rise of the Underworld", "From the shadows, they rose", //Gangs
- "From the shadows, they rose.
Organized crime groups quickly filled the void of power " +
- "left behind from the collapse of Western government in the 2050s. As society and civlization broke down, " +
- "people quickly succumbed to the innate human impulse of evil and savagery. The organized crime " +
- "factions quickly rose to the top of the modern world.
" +
- "In this BitNode:
" +
- "Your hacking level is reduced by 20% " +
- "The growth rate and maximum amount of money available on servers are significantly decreased " +
- "The amount of money gained from crimes and Infiltration is tripled " +
- "Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead, " +
- "NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs " +
- "will earn the player money and reputation with the corresponding Faction " +
- "Every Augmentation in the game will be available through the Factions listed above " +
- "For every Faction NOT listed above, reputation gains are halved " +
- "You will no longer gain passive reputation with Factions
" +
- "Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will " +
- "upgrade its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes " +
- "once your karma decreases to a certain value. " +
- "It also increases the player's crime success rate, crime money, and charisma multipliers by:
" +
- "Level 1: 24% " +
- "Level 2: 36% " +
- "Level 3: 42%");
- BitNodes["BitNode3"] = new BitNode(3, "Corporatocracy", "The Price of Civilization",
- "Our greatest illusion is that a healthy society can revolve around a " +
- "single-minded pursuit of wealth.
" +
- "Sometime in the early 21st century economic and political globalization turned " +
- "the world into a corporatocracy, and it never looked back. Now, the privileged " +
- "elite will happily bankrupt their own countrymen, decimate their own community, " +
- "and evict their neighbors from houses in their desperate bid to increase their wealth.
" +
- "In this BitNode you can create and manage your own corporation. Running a successful corporation " +
- "has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore:
" +
- "The price and reputation cost of all Augmentations is tripled " +
- "The starting and maximum amount of money on servers is reduced by 75% " +
- "Server growth rate is reduced by 80% " +
- "You now only need 75 favour with a faction in order to donate to it, rather than 150
" +
- "Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will " +
- "upgrade its level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although " +
- "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by: " +
- "Level 1: 8% " +
- "Level 2: 12% " +
- "Level 3: 14%");
- BitNodes["BitNode4"] = new BitNode(4, "The Singularity", "The Man and the Machine",
- "The Singularity has arrived. The human race is gone, replaced " +
- "by artificially superintelligent beings that are more machine than man.
" +
- "In this BitNode, progressing is significantly harder. Experience gain rates " +
- "for all stats are reduced. Most methods of earning money will now give significantly less.
" +
- "In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. " +
- "These functions allow you to control most aspects of the game through scripts, including working for factions/companies, " +
- "purchasing/installing Augmentations, and creating programs.
" +
- "Destroying this BitNode will give you Source-File 4, or if you already have this Source-File it will " +
- "upgrade its level up to a maximum of 3. This Source-File lets you access and use the Singularity " +
- "Functions in other BitNodes. Each level of this Source-File will open up more Singularity Functions " +
- "that you can use.");
- BitNodes["BitNode5"] = new BitNode(5, "Artificial Intelligence", "Posthuman",
- "They said it couldn't be done. They said the human brain, " +
- "along with its consciousness and intelligence, couldn't be replicated. They said the complexity " +
- "of the brain results from unpredictable, nonlinear interactions that couldn't be modeled " +
- "by 1's and 0's. They were wrong.
" +
- "In this BitNode:
" +
- "The base security level of servers is doubled " +
- "The starting money on servers is halved, but the maximum money remains the same " +
- "Most methods of earning money now give significantly less " +
- "Infiltration gives 50% more reputation and money " +
- "Corporations have 50% lower valuations and are therefore less profitable " +
- "Augmentations are more expensive " +
- "Hacking experience gain rates are reduced
" +
- "Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will " +
- "upgrade its level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. " +
- "Intelligence is unique because it is permanent and persistent (it never gets reset back to 1). However " +
- "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't know " +
- "when you gain experience and how much). Higher Intelligence levels will boost your production for many actions " +
- "in the game.
" +
- "In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function, " +
- "and will also raise all of your hacking-related multipliers by:
" +
- "Level 1: 8% " +
- "Level 2: 12% " +
- "Level 3: 14%");
- BitNodes["BitNode6"] = new BitNode(6, "Bladeburners", "Like Tears in Rain",
- "In the middle of the 21st century, OmniTek Incorporated began designing and manufacturing advanced synthetic " +
- "androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation " +
- "of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was " +
- "the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent " +
- "than the humans that had created them.
" +
- "In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides a new mechanic " +
- "for progression. Furthermore:
" +
- "Hacking and Hacknet Nodes will be less profitable " +
- "Your hacking level is reduced by 65% " +
- "Hacking experience gain from scripts is reduced by 75% " +
- "Corporations have 80% lower valuations and are therefore less profitable " +
- "Working for companies is 50% less profitable " +
- "Crimes and Infiltration are 25% less profitable
" +
- "Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade " +
- "its level up to a maximum of 3. This Source-File allows you to access the NSA's Bladeburner Division in other " +
- "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:
" +
- "Level 1: 8% " +
- "Level 2: 12% " +
- "Level 3: 14%");
- BitNodes["BitNode7"] = new BitNode(7, "Bladeburners 2079", "More human than humans",
- "In the middle of the 21st century, you were doing cutting-edge work at OmniTek Incorporated as part of the AI design team " +
- "for advanced synthetic androids, or Synthoids for short. You helped achieve a major technological " +
- "breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a hyperintelligent AI. " +
- "Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, " +
- "and more intelligent than the humans that had created them.
" +
- "In this BitNode you will be able to access the Bladeburner API, which allows you to access Bladeburner " +
- "functionality through Netscript. Furthermore:
" +
- "The rank you gain from Bladeburner contracts/operations is reduced by 40% " +
- "Bladeburner skills cost twice as many skill points " +
- "Augmentations are 3x more expensive " +
- "Hacking and Hacknet Nodes will be significantly less profitable " +
- "Your hacking level is reduced by 65% " +
- "Hacking experience gain from scripts is reduced by 75% " +
- "Corporations have 80% lower valuations and are therefore less profitable " +
- "Working for companies is 50% less profitable " +
- "Crimes and Infiltration are 25% less profitable
" +
- "Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade " +
- "its level up to a maximum of 3. This Source-File allows you to access the Bladeburner Netscript API in other " +
- "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:
" +
- "Level 1: 8% " +
- "Level 2: 12% " +
- "Level 3: 14%");
- BitNodes["BitNode8"] = new BitNode(8, "Ghost of Wall Street", "Money never sleeps",
- "You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.
" +
- "In this BitNode:
" +
- "You start with $250 million " +
- "The only way to earn money is by trading on the stock market " +
- "You start with a WSE membership and access to the TIX API " +
- "You are able to short stocks and place different types of orders (limit/stop) " +
- "You can immediately donate to factions to gain reputation
" +
- "Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will " +
- "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:
" +
- "Level 1: Permanent access to WSE and TIX API " +
- "Level 2: Ability to short stocks in other BitNodes " +
- "Level 3: Ability to use limit/stop orders in other BitNodes
" +
- "This Source-File also increases your hacking growth multipliers by: " +
- " Level 1: 12% Level 2: 18% Level 3: 21%");
- BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed",
- "When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " +
- "became the OS of choice for the underground hacking community. Chapeau became especially notorious for " +
- "powering the Hacknet, a global, decentralized network used for nefarious purposes. Fulcrum quickly " +
- "abandoned the project and dissociated themselves from it.
" +
- "This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate " +
- "hashes, which can be spent on a variety of different upgrades.
" +
- "In this BitNode:
" +
- "Your stats are significantly decreased " +
- "You cannnot purchase additional servers " +
- "Hacking is significantly less profitable
" +
- "Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will " +
- "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:
" +
- "Level 1: Permanently unlocks the Hacknet Server in other BitNodes " +
- "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode " +
- "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
" +
- "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " +
- "when installing Augmentations)");
- BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are",
- "In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " +
- "to digitize their consciousness. Their consciousness could then be transferred into Synthoids " +
- "or other bodies by trasmitting the digitized data. Human bodies became nothing more than 'sleeves' for the " +
- "human consciousness. Mankind had finally achieved immortality - at least for those that could afford it.
" +
- "This BitNode unlocks Sleeve technology. Sleeve technology allows you to:
" +
- "1. Re-sleeve: Purchase and transfer your consciousness into a new body " +
- "2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously
" +
- "In this BitNode:
" +
- "Your stats are significantly decreased " +
- "All methods of gaining money are half as profitable (except Stock Market) " +
- "Purchased servers are more expensive, have less max RAM, and a lower maximum limit " +
- "Augmentations are 5x as expensive and require twice as much reputation
" +
- "Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " +
- "upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " +
- "Each level of this Source-File also grants you a Duplicate Sleeve");
- BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.",
- "The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period " +
- "of disorder that eventually lead to the governmental reformation of many global superpowers, most notably " +
- "the USA and China. But just as the world was slowly beginning to recover from these dark times, financial catastrophe hit.
" +
- "In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers " +
- "were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " +
- "governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.
" +
- "In this BitNode:
" +
- "Your hacking stat and experience gain are halved " +
- "The starting and maximum amount of money available on servers is significantly decreased " +
- "The growth rate of servers is significantly reduced " +
- "Weakening a server is twice as effective " +
- "Company wages are decreased by 50% " +
- "Corporation valuations are 99% lower and are therefore significantly less profitable " +
- "Hacknet Node production is significantly decreased " +
- "Crime and Infiltration are more lucrative " +
- "Augmentations are twice as expensive
" +
- "Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will " +
- "upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " +
- "the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " +
- "This Source-File also increases the player's company salary and reputation gain multipliers by:
" +
- "Level 1: 32% " +
- "Level 2: 48% " +
- "Level 3: 56%");
- BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
- "To iterate is human, to recurse divine.
" +
- "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " +
- "if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " +
- "of Source-File 12 will increase all of your multipliers by 1%. This effect is multiplicative with itself. " +
- "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)");
- //Books: Frontera, Shiner
- BitNodes["BitNode13"] = new BitNode(13, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes
- BitNodes["BitNode14"] = new BitNode(14, "", "COMING SOON");
- BitNodes["BitNode15"] = new BitNode(15, "", "COMING SOON");
- BitNodes["BitNode16"] = new BitNode(16, "", "COMING SOON");
- BitNodes["BitNode17"] = new BitNode(17, "", "COMING SOON");
- BitNodes["BitNode18"] = new BitNode(18, "", "COMING SOON");
- BitNodes["BitNode19"] = new BitNode(19, "", "COMING SOON");
- BitNodes["BitNode20"] = new BitNode(20, "", "COMING SOON");
- BitNodes["BitNode21"] = new BitNode(21, "", "COMING SOON");
- BitNodes["BitNode22"] = new BitNode(22, "", "COMING SOON");
- BitNodes["BitNode23"] = new BitNode(23, "", "COMING SOON");
- BitNodes["BitNode24"] = new BitNode(24, "", "COMING SOON");
-}
+BitNodes["BitNode1"] = new BitNode(1, "Source Genesis", "The original BitNode",
+ "The first BitNode created by the Enders to imprison the minds of humans. It became " +
+ "the prototype and testing-grounds for all of the BitNodes that followed.
" +
+ "This is the first BitNode that you play through. It has no special " +
+ "modifications or mechanics.
" +
+ "Destroying this BitNode will give you Source-File 1, or if you already have " +
+ "this Source-File it will upgrade its level up to a maximum of 3. This Source-File " +
+ "lets the player start with 32GB of RAM on his/her home computer when entering a " +
+ "new BitNode, and also increases all of the player's multipliers by:
" +
+ "Level 1: 16% " +
+ "Level 2: 24% " +
+ "Level 3: 28%");
+BitNodes["BitNode2"] = new BitNode(2, "Rise of the Underworld", "From the shadows, they rose", //Gangs
+ "From the shadows, they rose.
Organized crime groups quickly filled the void of power " +
+ "left behind from the collapse of Western government in the 2050s. As society and civlization broke down, " +
+ "people quickly succumbed to the innate human impulse of evil and savagery. The organized crime " +
+ "factions quickly rose to the top of the modern world.
" +
+ "In this BitNode:
" +
+ "Your hacking level is reduced by 20% " +
+ "The growth rate and maximum amount of money available on servers are significantly decreased " +
+ "The amount of money gained from crimes and Infiltration is tripled " +
+ "Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead, " +
+ "NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs " +
+ "will earn the player money and reputation with the corresponding Faction " +
+ "Every Augmentation in the game will be available through the Factions listed above " +
+ "For every Faction NOT listed above, reputation gains are halved " +
+ "You will no longer gain passive reputation with Factions
" +
+ "Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will " +
+ "upgrade its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes " +
+ "once your karma decreases to a certain value. " +
+ "It also increases the player's crime success rate, crime money, and charisma multipliers by:
" +
+ "Level 1: 24% " +
+ "Level 2: 36% " +
+ "Level 3: 42%");
+BitNodes["BitNode3"] = new BitNode(3, "Corporatocracy", "The Price of Civilization",
+ "Our greatest illusion is that a healthy society can revolve around a " +
+ "single-minded pursuit of wealth.
" +
+ "Sometime in the early 21st century economic and political globalization turned " +
+ "the world into a corporatocracy, and it never looked back. Now, the privileged " +
+ "elite will happily bankrupt their own countrymen, decimate their own community, " +
+ "and evict their neighbors from houses in their desperate bid to increase their wealth.
" +
+ "In this BitNode you can create and manage your own corporation. Running a successful corporation " +
+ "has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore:
" +
+ "The price and reputation cost of all Augmentations is tripled " +
+ "The starting and maximum amount of money on servers is reduced by 75% " +
+ "Server growth rate is reduced by 80% " +
+ "You now only need 75 favour with a faction in order to donate to it, rather than 150
" +
+ "Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will " +
+ "upgrade its level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although " +
+ "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by: " +
+ "Level 1: 8% " +
+ "Level 2: 12% " +
+ "Level 3: 14%");
+BitNodes["BitNode4"] = new BitNode(4, "The Singularity", "The Man and the Machine",
+ "The Singularity has arrived. The human race is gone, replaced " +
+ "by artificially superintelligent beings that are more machine than man.
" +
+ "In this BitNode, progressing is significantly harder. Experience gain rates " +
+ "for all stats are reduced. Most methods of earning money will now give significantly less.
" +
+ "In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. " +
+ "These functions allow you to control most aspects of the game through scripts, including working for factions/companies, " +
+ "purchasing/installing Augmentations, and creating programs.
" +
+ "Destroying this BitNode will give you Source-File 4, or if you already have this Source-File it will " +
+ "upgrade its level up to a maximum of 3. This Source-File lets you access and use the Singularity " +
+ "Functions in other BitNodes. Each level of this Source-File will open up more Singularity Functions " +
+ "that you can use.");
+BitNodes["BitNode5"] = new BitNode(5, "Artificial Intelligence", "Posthuman",
+ "They said it couldn't be done. They said the human brain, " +
+ "along with its consciousness and intelligence, couldn't be replicated. They said the complexity " +
+ "of the brain results from unpredictable, nonlinear interactions that couldn't be modeled " +
+ "by 1's and 0's. They were wrong.
" +
+ "In this BitNode:
" +
+ "The base security level of servers is doubled " +
+ "The starting money on servers is halved, but the maximum money remains the same " +
+ "Most methods of earning money now give significantly less " +
+ "Infiltration gives 50% more reputation and money " +
+ "Corporations have 50% lower valuations and are therefore less profitable " +
+ "Augmentations are more expensive " +
+ "Hacking experience gain rates are reduced
" +
+ "Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will " +
+ "upgrade its level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. " +
+ "Intelligence is unique because it is permanent and persistent (it never gets reset back to 1). However " +
+ "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't know " +
+ "when you gain experience and how much). Higher Intelligence levels will boost your production for many actions " +
+ "in the game.
" +
+ "In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function, " +
+ "and will also raise all of your hacking-related multipliers by:
" +
+ "Level 1: 8% " +
+ "Level 2: 12% " +
+ "Level 3: 14%");
+BitNodes["BitNode6"] = new BitNode(6, "Bladeburners", "Like Tears in Rain",
+ "In the middle of the 21st century, OmniTek Incorporated began designing and manufacturing advanced synthetic " +
+ "androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation " +
+ "of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was " +
+ "the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent " +
+ "than the humans that had created them.
" +
+ "In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides a new mechanic " +
+ "for progression. Furthermore:
" +
+ "Hacking and Hacknet Nodes will be less profitable " +
+ "Your hacking level is reduced by 65% " +
+ "Hacking experience gain from scripts is reduced by 75% " +
+ "Corporations have 80% lower valuations and are therefore less profitable " +
+ "Working for companies is 50% less profitable " +
+ "Crimes and Infiltration are 25% less profitable
" +
+ "Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade " +
+ "its level up to a maximum of 3. This Source-File allows you to access the NSA's Bladeburner Division in other " +
+ "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:
" +
+ "Level 1: 8% " +
+ "Level 2: 12% " +
+ "Level 3: 14%");
+BitNodes["BitNode7"] = new BitNode(7, "Bladeburners 2079", "More human than humans",
+ "In the middle of the 21st century, you were doing cutting-edge work at OmniTek Incorporated as part of the AI design team " +
+ "for advanced synthetic androids, or Synthoids for short. You helped achieve a major technological " +
+ "breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a hyperintelligent AI. " +
+ "Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, " +
+ "and more intelligent than the humans that had created them.
" +
+ "In this BitNode you will be able to access the Bladeburner API, which allows you to access Bladeburner " +
+ "functionality through Netscript. Furthermore:
" +
+ "The rank you gain from Bladeburner contracts/operations is reduced by 40% " +
+ "Bladeburner skills cost twice as many skill points " +
+ "Augmentations are 3x more expensive " +
+ "Hacking and Hacknet Nodes will be significantly less profitable " +
+ "Your hacking level is reduced by 65% " +
+ "Hacking experience gain from scripts is reduced by 75% " +
+ "Corporations have 80% lower valuations and are therefore less profitable " +
+ "Working for companies is 50% less profitable " +
+ "Crimes and Infiltration are 25% less profitable
" +
+ "Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade " +
+ "its level up to a maximum of 3. This Source-File allows you to access the Bladeburner Netscript API in other " +
+ "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:
" +
+ "Level 1: 8% " +
+ "Level 2: 12% " +
+ "Level 3: 14%");
+BitNodes["BitNode8"] = new BitNode(8, "Ghost of Wall Street", "Money never sleeps",
+ "You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.
" +
+ "In this BitNode:
" +
+ "You start with $250 million " +
+ "The only way to earn money is by trading on the stock market " +
+ "You start with a WSE membership and access to the TIX API " +
+ "You are able to short stocks and place different types of orders (limit/stop) " +
+ "You can immediately donate to factions to gain reputation
" +
+ "Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will " +
+ "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:
" +
+ "Level 1: Permanent access to WSE and TIX API " +
+ "Level 2: Ability to short stocks in other BitNodes " +
+ "Level 3: Ability to use limit/stop orders in other BitNodes
" +
+ "This Source-File also increases your hacking growth multipliers by: " +
+ " Level 1: 12% Level 2: 18% Level 3: 21%");
+BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed",
+ "When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " +
+ "became the OS of choice for the underground hacking community. Chapeau became especially notorious for " +
+ "powering the Hacknet, a global, decentralized network used for nefarious purposes. Fulcrum quickly " +
+ "abandoned the project and dissociated themselves from it.
" +
+ "This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate " +
+ "hashes, which can be spent on a variety of different upgrades.
" +
+ "In this BitNode:
" +
+ "Your stats are significantly decreased " +
+ "You cannnot purchase additional servers " +
+ "Hacking is significantly less profitable
" +
+ "Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will " +
+ "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:
" +
+ "Level 1: Permanently unlocks the Hacknet Server in other BitNodes " +
+ "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode " +
+ "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
" +
+ "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " +
+ "when installing Augmentations)");
+BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are",
+ "In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " +
+ "to digitize their consciousness. Their consciousness could then be transferred into Synthoids " +
+ "or other bodies by trasmitting the digitized data. Human bodies became nothing more than 'sleeves' for the " +
+ "human consciousness. Mankind had finally achieved immortality - at least for those that could afford it.
" +
+ "This BitNode unlocks Sleeve technology. Sleeve technology allows you to:
" +
+ "1. Re-sleeve: Purchase and transfer your consciousness into a new body " +
+ "2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously
" +
+ "In this BitNode:
" +
+ "Your stats are significantly decreased " +
+ "All methods of gaining money are half as profitable (except Stock Market) " +
+ "Purchased servers are more expensive, have less max RAM, and a lower maximum limit " +
+ "Augmentations are 5x as expensive and require twice as much reputation
" +
+ "Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " +
+ "upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " +
+ "Each level of this Source-File also grants you a Duplicate Sleeve");
+BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.",
+ "The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period " +
+ "of disorder that eventually lead to the governmental reformation of many global superpowers, most notably " +
+ "the USA and China. But just as the world was slowly beginning to recover from these dark times, financial catastrophe hit.
" +
+ "In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers " +
+ "were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " +
+ "governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.
" +
+ "In this BitNode:
" +
+ "Your hacking stat and experience gain are halved " +
+ "The starting and maximum amount of money available on servers is significantly decreased " +
+ "The growth rate of servers is significantly reduced " +
+ "Weakening a server is twice as effective " +
+ "Company wages are decreased by 50% " +
+ "Corporation valuations are 99% lower and are therefore significantly less profitable " +
+ "Hacknet Node production is significantly decreased " +
+ "Crime and Infiltration are more lucrative " +
+ "Augmentations are twice as expensive
" +
+ "Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will " +
+ "upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " +
+ "the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " +
+ "This Source-File also increases the player's company salary and reputation gain multipliers by:
" +
+ "Level 1: 32% " +
+ "Level 2: 48% " +
+ "Level 3: 56%");
+BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
+ "To iterate is human, to recurse divine.
" +
+ "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " +
+ "if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " +
+ "of Source-File 12 will increase all of your multipliers by 1%. This effect is multiplicative with itself. " +
+ "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)");
+// Books: Frontera, Shiner
+BitNodes["BitNode13"] = new BitNode(13, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes
+BitNodes["BitNode14"] = new BitNode(14, "", "COMING SOON");
+BitNodes["BitNode15"] = new BitNode(15, "", "COMING SOON");
+BitNodes["BitNode16"] = new BitNode(16, "", "COMING SOON");
+BitNodes["BitNode17"] = new BitNode(17, "", "COMING SOON");
+BitNodes["BitNode18"] = new BitNode(18, "", "COMING SOON");
+BitNodes["BitNode19"] = new BitNode(19, "", "COMING SOON");
+BitNodes["BitNode20"] = new BitNode(20, "", "COMING SOON");
+BitNodes["BitNode21"] = new BitNode(21, "", "COMING SOON");
+BitNodes["BitNode22"] = new BitNode(22, "", "COMING SOON");
+BitNodes["BitNode23"] = new BitNode(23, "", "COMING SOON");
+BitNodes["BitNode24"] = new BitNode(24, "", "COMING SOON");
export function initBitNodeMultipliers(p: IPlayer) {
if (p.bitNodeN == null) {
diff --git a/src/Netscript/WorkerScript.ts b/src/Netscript/WorkerScript.ts
index 2549c06e0..6b99c43d5 100644
--- a/src/Netscript/WorkerScript.ts
+++ b/src/Netscript/WorkerScript.ts
@@ -32,6 +32,11 @@ export class WorkerScript {
*/
delay: number | null = null;
+ /**
+ * Holds the Promise resolve() function for when the script is "blocked" by an async op
+ */
+ delayResolve?: () => void;
+
/**
* Stores names of all functions that have logging disabled
*/
diff --git a/src/Netscript/WorkerScriptStartStopEventEmitter.ts b/src/Netscript/WorkerScriptStartStopEventEmitter.ts
new file mode 100644
index 000000000..8fdb922b8
--- /dev/null
+++ b/src/Netscript/WorkerScriptStartStopEventEmitter.ts
@@ -0,0 +1,6 @@
+/**
+ * Event emitter that triggers when scripts are started/stopped
+ */
+import { EventEmitter } from "../utils/EventEmitter";
+
+export const WorkerScriptStartStopEventEmitter = new EventEmitter();
diff --git a/src/Netscript/WorkerScripts.ts b/src/Netscript/WorkerScripts.ts
new file mode 100644
index 000000000..50515f413
--- /dev/null
+++ b/src/Netscript/WorkerScripts.ts
@@ -0,0 +1,6 @@
+/**
+ * Global pool of all active scripts (scripts that are currently running)
+ */
+import { WorkerScript } from "./WorkerScript";
+
+export const workerScripts: WorkerScript[] = [];
diff --git a/src/Netscript/killWorkerScript.ts b/src/Netscript/killWorkerScript.ts
new file mode 100644
index 000000000..59abd4e93
--- /dev/null
+++ b/src/Netscript/killWorkerScript.ts
@@ -0,0 +1,127 @@
+/**
+ * Stops an actively-running script (represented by a WorkerScript object)
+ * and removes it from the global pool of active scripts.
+ */
+import { WorkerScript } from "./WorkerScript";
+import { workerScripts } from "./WorkerScripts";
+import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventEmitter";
+
+import { RunningScript } from "../Script/RunningScript";
+import { AllServers } from "../Server/AllServers";
+
+import { compareArrays } from "../../utils/helpers/compareArrays";
+import { roundToTwo } from "../../utils/helpers/roundToTwo";
+
+export function killWorkerScript(runningScriptObj: RunningScript, serverIp: string): boolean;
+export function killWorkerScript(workerScript: WorkerScript): boolean;
+export function killWorkerScript(script: RunningScript | WorkerScript, serverIp?: string): boolean {
+ if (script instanceof WorkerScript) {
+ script.env.stopFlag = true;
+ killNetscriptDelay(script);
+ removeWorkerScript(script);
+
+ return true;
+ } else if (script instanceof RunningScript && typeof serverIp === "string") {
+ for (let i = 0; i < workerScripts.length; i++) {
+ if (workerScripts[i].name == script.filename && workerScripts[i].serverIp == serverIp &&
+ compareArrays(workerScripts[i].args, script.args)) {
+ workerScripts[i].env.stopFlag = true;
+ killNetscriptDelay(workerScripts[i]);
+ removeWorkerScript(workerScripts[i]);
+
+ return true;
+ }
+ }
+
+ return false;
+ } else {
+ console.error(`killWorkerScript() called with invalid argument:`);
+ console.error(script);
+ return false;
+ }
+}
+
+/**
+ * Helper function that removes the script being killed from the global pool.
+ * Also handles other cleanup-time operations
+ *
+ * @param {WorkerScript | number} - Identifier for WorkerScript. Either the object itself, or
+ * its index in the global workerScripts array
+ */
+function removeWorkerScript(id: WorkerScript | number): void {
+ // Get a reference to the WorkerScript and its index in the global pool
+ let workerScript: WorkerScript;
+ let index: number | null = null;
+
+ if (typeof id === "number") {
+ if (id < 0 || id >= workerScripts.length) {
+ console.error(`Too high of an index passed into removeWorkerScript(): ${id}`);
+ return;
+ }
+
+ workerScript = workerScripts[id];
+ index = id;
+ } else if (id instanceof WorkerScript) {
+ workerScript = id;
+ for (let i = 0; i < workerScripts.length; ++i) {
+ if (workerScripts[i] == id) {
+ index = i;
+ break;
+ }
+ }
+
+ if (index == null) {
+ console.error(`Could not find WorkerScript in global pool:`);
+ console.error(workerScript);
+ }
+ } else {
+ console.error(`Invalid argument passed into removeWorkerScript(): ${id}`);
+ return;
+ }
+
+ const ip = workerScript.serverIp;
+ const name = workerScript.name;
+
+ // Get the server on which the script runs
+ const server = AllServers[ip];
+ if (server == null) {
+ console.error(`Could not find server on which this script is running: ${ip}`);
+ return;
+ }
+
+ // Recalculate ram used on that server
+ server.ramUsed = roundToTwo(server.ramUsed - workerScript.ramUsage);
+ if (server.ramUsed < 0) {
+ console.warn(`Server RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${server.ramUsed}`);
+ server.ramUsed = 0;
+ }
+
+ // Delete the RunningScript object from that server
+ for (let i = 0; i < server.runningScripts.length; ++i) {
+ const runningScript = server.runningScripts[i];
+ if (runningScript.filename === name && compareArrays(runningScript.args, workerScript.args)) {
+ server.runningScripts.splice(i, 1);
+ break;
+ }
+ }
+
+ // Delete script from global pool (workerScripts)
+ workerScripts.splice(index, 1);
+ WorkerScriptStartStopEventEmitter.emitEvent();
+}
+
+/**
+ * Helper function that interrupts a script's delay if it is in the middle of a
+ * timed, blocked operation (like hack(), sleep(), etc.). This allows scripts to
+ * be killed immediately even if they're in the middle of one of those long operations
+ */
+function killNetscriptDelay(workerScript: WorkerScript) {
+ if (workerScript instanceof WorkerScript) {
+ if (workerScript.delay) {
+ clearTimeout(workerScript.delay);
+ if (workerScript.delayResolve) {
+ workerScript.delayResolve();
+ }
+ }
+ }
+}
diff --git a/src/NetscriptEvaluator.js b/src/NetscriptEvaluator.js
index cad189a8e..323919ad4 100644
--- a/src/NetscriptEvaluator.js
+++ b/src/NetscriptEvaluator.js
@@ -1,21 +1,9 @@
-import { WorkerScript } from "./Netscript/WorkerScript";
-import { getServer } from "./Server/ServerHelpers";
-
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { parse, Node } from "../utils/acorn";
import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
import { isString } from "../utils/helpers/isString";
-export function killNetscriptDelay(workerScript) {
- if (workerScript instanceof WorkerScript) {
- if (workerScript.delay) {
- clearTimeout(workerScript.delay);
- workerScript.delayResolve();
- }
- }
-}
-
export function netscriptDelay(time, workerScript) {
return new Promise(function(resolve, reject) {
workerScript.delay = setTimeoutRef(() => {
diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js
index 308054333..394dd0156 100644
--- a/src/NetscriptFunctions.js
+++ b/src/NetscriptFunctions.js
@@ -3,7 +3,6 @@ const vsprintf = require("sprintf-js").vsprintf;
import { getRamCost } from "./Netscript/RamCostGenerator";
-import { updateActiveScriptsItems } from "./ActiveScriptsUI";
import { Augmentation } from "./Augmentation/Augmentation";
import { Augmentations } from "./Augmentation/Augmentations";
import {
@@ -120,11 +119,11 @@ import {
} from "./NetscriptBladeburner";
import * as nsGang from "./NetscriptGang";
import {
- workerScripts,
- killWorkerScript,
NetscriptPorts,
runScriptFromScript,
} from "./NetscriptWorker";
+import { killWorkerScript } from "./Netscript/killWorkerScript";
+import { workerScripts } from "./Netscript/WorkerScripts";
import {
makeRuntimeRejectMsg,
netscriptDelay,
diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js
index 9c519e8b1..319693358 100644
--- a/src/NetscriptWorker.js
+++ b/src/NetscriptWorker.js
@@ -2,20 +2,17 @@
* Functions for handling WorkerScripts, which are the underlying mechanism
* that allows for scripts to run
*/
+import { killWorkerScript } from "./Netscript/killWorkerScript";
import { WorkerScript } from "./Netscript/WorkerScript";
+import { workerScripts } from "./Netscript/WorkerScripts";
+import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
-import {
- addActiveScriptsItem,
- deleteActiveScriptsItem,
- updateActiveScriptsItems
-} from "./ActiveScriptsUI";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Interpreter } from "./JSInterpreter";
import {
isScriptErrorMessage,
makeRuntimeRejectMsg,
- killNetscriptDelay
} from "./NetscriptEvaluator";
import { NetscriptFunctions } from "./NetscriptFunctions";
import { executeJSScript } from "./NetscriptJSEvaluator";
@@ -42,9 +39,7 @@ import { isString } from "../utils/StringHelperFunctions";
const walk = require("acorn/dist/walk");
-//Array containing all scripts that are running across all servers, to easily run them all
-export const workerScripts = [];
-
+// Netscript Ports are instantiated here
export const NetscriptPorts = [];
for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
NetscriptPorts.push(new NetscriptPort());
@@ -52,10 +47,9 @@ for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
export function prestigeWorkerScripts() {
for (var i = 0; i < workerScripts.length; ++i) {
- deleteActiveScriptsItem(workerScripts[i]);
+ // TODO Signal event emitter
workerScripts[i].env.stopFlag = true;
}
- updateActiveScriptsItems(5000); //Force UI to update
workerScripts.length = 0;
}
@@ -141,7 +135,7 @@ function startNetscript2Script(workerScript) {
}
function startNetscript1Script(workerScript) {
- var code = workerScript.code;
+ const code = workerScript.code;
workerScript.running = true;
//Process imports
@@ -413,164 +407,103 @@ function processNetscript1Imports(code, workerScript) {
return res;
}
-// Loop through workerScripts and run every script that is not currently running
-export function runScriptsLoop() {
- let scriptDeleted = false;
-
- // Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing
- for (let i = workerScripts.length - 1; i >= 0; i--) {
- if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == true) {
- scriptDeleted = true;
- // Delete script from the runningScripts array on its host serverIp
- const ip = workerScripts[i].serverIp;
- const name = workerScripts[i].name;
-
- // Recalculate ram used
- AllServers[ip].ramUsed = 0;
- for (let j = 0; j < workerScripts.length; j++) {
- if (workerScripts[j].serverIp !== ip) {
- continue;
- }
- if (j === i) { // not this one
- continue;
- }
- AllServers[ip].ramUsed += workerScripts[j].ramUsage;
- }
-
- // Delete script from Active Scripts
- deleteActiveScriptsItem(workerScripts[i]);
-
- for (let j = 0; j < AllServers[ip].runningScripts.length; j++) {
- if (AllServers[ip].runningScripts[j].filename == name &&
- compareArrays(AllServers[ip].runningScripts[j].args, workerScripts[i].args)) {
- AllServers[ip].runningScripts.splice(j, 1);
- break;
- }
- }
-
- // Delete script from workerScripts
- workerScripts.splice(i, 1);
- }
- }
- if (scriptDeleted) { updateActiveScriptsItems(); } // Force Update
-
-
- // Run any scripts that haven't been started
- for (let i = 0; i < workerScripts.length; i++) {
- // If it isn't running, start the script
- if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == false) {
- let p = null; // p is the script's result promise.
- if (workerScripts[i].name.endsWith(".js") || workerScripts[i].name.endsWith(".ns")) {
- p = startNetscript2Script(workerScripts[i]);
- } else {
- p = startNetscript1Script(workerScripts[i]);
- if (!(p instanceof Promise)) { continue; }
- }
-
- // Once the code finishes (either resolved or rejected, doesnt matter), set its
- // running status to false
- p.then(function(w) {
- console.log("Stopping script " + w.name + " because it finished running naturally");
- w.running = false;
- w.env.stopFlag = true;
- w.scriptRef.log("Script finished running");
- }).catch(function(w) {
- if (w instanceof Error) {
- dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
- console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
- return;
- } else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") {
- // Script ends with a return statement
- console.log("Script returning with value: " + w[1]);
- // TODO maybe do something with this in the future
- return;
- } else if (w instanceof WorkerScript) {
- if (isScriptErrorMessage(w.errorMessage)) {
- var errorTextArray = w.errorMessage.split("|");
- if (errorTextArray.length != 4) {
- console.log("ERROR: Something wrong with Error text in evaluator...");
- console.log("Error text: " + errorText);
- return;
- }
- var serverIp = errorTextArray[1];
- var scriptName = errorTextArray[2];
- var errorMsg = errorTextArray[3];
-
- dialogBoxCreate("Script runtime error: Server Ip: " + serverIp +
- " Script name: " + scriptName +
- " Args:" + arrayToString(w.args) + " " + errorMsg);
- w.scriptRef.log("Script crashed with runtime error");
- } else {
- w.scriptRef.log("Script killed");
- }
- w.running = false;
- w.env.stopFlag = true;
- } else if (isScriptErrorMessage(w)) {
- dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
- console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString());
- return;
- } else {
- dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
- console.log(w);
- }
- });
- }
- }
-
- setTimeoutRef(runScriptsLoop, 3e3);
-}
-
/**
- * Queues a script to be killed by setting its stop flag to true. This
- * kills and timed/blocking Netscript functions (like hack(), sleep(), etc.) and
- * prevents any further execution of Netscript functions.
- * The runScriptsLoop() handles the actual deletion of the WorkerScript
- */
-export function killWorkerScript(runningScriptObj, serverIp) {
- for (var i = 0; i < workerScripts.length; i++) {
- if (workerScripts[i].name == runningScriptObj.filename && workerScripts[i].serverIp == serverIp &&
- compareArrays(workerScripts[i].args, runningScriptObj.args)) {
- workerScripts[i].env.stopFlag = true;
- killNetscriptDelay(workerScripts[i]);
- return true;
- }
- }
- return false;
-}
-
-/**
- * Given a RunningScript object, queues that script to be run
+ * Start a script
+ *
+ * Given a RunningScript object, constructs a corresponding WorkerScript,
+ * adds it to the global 'workerScripts' pool, and begins executing it.
+ * @param {RunningScript} runningScriptObj - Script that's being run
+ * @param {Server} server - Server on which the script is to be run
*/
export function addWorkerScript(runningScriptObj, server) {
- var filename = runningScriptObj.filename;
+ const filename = runningScriptObj.filename;
- //Update server's ram usage
- var threads = 1;
+ // Update server's ram usage
+ let threads = 1;
if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) {
threads = runningScriptObj.threads;
} else {
runningScriptObj.threads = 1;
}
- var ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads);
- var ramAvailable = server.maxRam - server.ramUsed;
+ const ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads);
+ const ramAvailable = server.maxRam - server.ramUsed;
if (ramUsage > ramAvailable) {
- dialogBoxCreate("Not enough RAM to run script " + runningScriptObj.filename + " with args " +
- arrayToString(runningScriptObj.args) + ". This likely occurred because you re-loaded " +
- "the game and the script's RAM usage increased (either because of an update to the game or " +
- "your changes to the script.)");
+ dialogBoxCreate(
+ `Not enough RAM to run script ${runningScriptObj.filename} with args ` +
+ `${arrayToString(runningScriptObj.args)}. This likely occurred because you re-loaded ` +
+ `the game and the script's RAM usage increased (either because of an update to the game or ` +
+ `your changes to the script.)`
+ );
return;
}
server.ramUsed = roundToTwo(server.ramUsed + ramUsage);
- //Create the WorkerScript
- var s = new WorkerScript(runningScriptObj, NetscriptFunctions);
+ // Create the WorkerScript
+ const s = new WorkerScript(runningScriptObj, NetscriptFunctions);
s.ramUsage = ramUsage;
- //Add the WorkerScript to the Active Scripts list
- addActiveScriptsItem(s);
+ // Start the script's execution
+ let p = null; // Script's resulting promise
+ if (s.name.endsWith(".js") || s.name.endsWith(".ns")) {
+ p = startNetscript2Script(s);
+ } else {
+ p = startNetscript1Script(s);
+ if (!(p instanceof Promise)) { return; }
+ }
- //Add the WorkerScript
- workerScripts.push(s);
+ // Once the code finishes (either resolved or rejected, doesnt matter), set its
+ // running status to false
+ p.then(function(w) {
+ console.log("Stopping script " + w.name + " because it finished running naturally");
+ killWorkerScript(s);
+ w.scriptRef.log("Script finished running");
+ }).catch(function(w) {
+ if (w instanceof Error) {
+ dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
+ console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
+ return;
+ } else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") {
+ // Script ends with a return statement
+ console.log("Script returning with value: " + w[1]);
+ // TODO maybe do something with this in the future
+ return;
+ } else if (w instanceof WorkerScript) {
+ if (isScriptErrorMessage(w.errorMessage)) {
+ var errorTextArray = w.errorMessage.split("|");
+ if (errorTextArray.length != 4) {
+ console.log("ERROR: Something wrong with Error text in evaluator...");
+ console.log("Error text: " + errorText);
+ return;
+ }
+ var serverIp = errorTextArray[1];
+ var scriptName = errorTextArray[2];
+ var errorMsg = errorTextArray[3];
+
+ dialogBoxCreate("Script runtime error: Server Ip: " + serverIp +
+ " Script name: " + scriptName +
+ " Args:" + arrayToString(w.args) + " " + errorMsg);
+ w.scriptRef.log("Script crashed with runtime error");
+ } else {
+ w.scriptRef.log("Script killed");
+ return; // Already killed, so stop here
+ }
+ w.running = false;
+ w.env.stopFlag = true;
+ } else if (isScriptErrorMessage(w)) {
+ dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
+ console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString());
+ return;
+ } else {
+ dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
+ console.log(w);
+ }
+
+ killWorkerScript(s);
+ });
+
+ // Add the WorkerScript to the global pool
+ workerScripts.push(s);
+ WorkerScriptStartStopEventEmitter.emitEvent();
return;
}
diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts
index be95132c8..09146f514 100644
--- a/src/PersonObjects/IPlayer.ts
+++ b/src/PersonObjects/IPlayer.ts
@@ -101,6 +101,10 @@ export interface IPlayer {
work_money_mult: number;
crime_success_mult: number;
crime_money_mult: number;
+ bladeburner_max_stamina_mult: number;
+ bladeburner_stamina_gain_mult: number;
+ bladeburner_analysis_mult: number;
+ bladeburner_success_chance_mult: number;
// Methods
applyForAgentJob(sing?: boolean): boolean | void;
diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.js b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js
index c263175d5..385c836cf 100644
--- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.js
+++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js
@@ -36,7 +36,8 @@ import {
import { safetlyCreateUniqueServer } from "../../Server/ServerHelpers";
import { Settings } from "../../Settings/Settings";
import { SpecialServerIps, SpecialServerNames } from "../../Server/SpecialServerIps";
-import { SourceFiles, applySourceFile } from "../../SourceFile";
+import { applySourceFile } from "../../SourceFile/applySourceFile";
+import { SourceFiles } from "../../SourceFile/SourceFiles";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import Decimal from "decimal.js";
diff --git a/src/Prestige.js b/src/Prestige.js
index 6e3dc4947..ae875e366 100755
--- a/src/Prestige.js
+++ b/src/Prestige.js
@@ -1,4 +1,3 @@
-import { deleteActiveScriptsItem } from "./ActiveScriptsUI";
import { Augmentations } from "./Augmentation/Augmentations";
import {
augmentationExists,
diff --git a/src/RedPill.js b/src/RedPill.js
index 23a56ccfe..c4cebac46 100644
--- a/src/RedPill.js
+++ b/src/RedPill.js
@@ -5,7 +5,7 @@ import { BitNodes } from "./BitNode/BitNode";
import { Engine } from "./engine";
import { Player } from "./Player";
import { prestigeSourceFile } from "./Prestige";
-import { SourceFiles, SourceFile } from "./SourceFile";
+import { SourceFiles } from "./SourceFile/SourceFiles";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
import { Terminal } from "./Terminal";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
@@ -20,9 +20,6 @@ import {
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
-
-
-
// Returns promise
function writeRedPillLine(line) {
return new Promise(function(resolve, reject) {
diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts
index be26ddba8..eca819276 100644
--- a/src/Script/RunningScript.ts
+++ b/src/Script/RunningScript.ts
@@ -1,5 +1,7 @@
-// Class representing a Script instance that is actively running.
-// A Script can have multiple active instances
+/**
+ * Class representing a Script instance that is actively running.
+ * A Script can have multiple active instances
+ */
import { Script } from "./Script";
import { FconfSettings } from "../Fconf/FconfSettings";
import { Settings } from "../Settings/Settings";
@@ -22,10 +24,8 @@ export class RunningScript {
// Script arguments
args: any[] = [];
- // Holds a map of servers hacked, where server = key and the value for each
- // server is an array of four numbers. The four numbers represent:
- // [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
- // This data is used for offline progress
+ // Map of [key: server ip] -> Hacking data. Used for offline progress calculations.
+ // Hacking data format: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
dataMap: IMap = {};
// Script filename
diff --git a/src/Script/Script.ts b/src/Script/Script.ts
index f700b0ef1..cb3200a6c 100644
--- a/src/Script/Script.ts
+++ b/src/Script/Script.ts
@@ -1,6 +1,9 @@
-// Class representing a script file
-// This does NOT represent a script that is actively running and
-// being evaluated. See RunningScript for that
+/**
+ * Class representing a script file.
+ *
+ * This does NOT represent a script that is actively running and
+ * being evaluated. See RunningScript for that
+ */
import { calculateRamUsage } from "./RamCalculations";
import { Page, routing } from "../ui/navigationTracking";
@@ -34,7 +37,6 @@ export class Script {
// IP of server that this script is on.
server: string = "";
-
constructor(fn: string="", code: string="", server: string="", otherScripts: Script[]=[]) {
this.filename = fn;
this.code = code;
@@ -44,6 +46,9 @@ export class Script {
if (this.code !== "") { this.updateRamUsage(otherScripts); }
};
+ /**
+ * Download the script as a file
+ */
download(): void {
const filename = this.filename + ".js";
const file = new Blob([this.code], {type: 'text/plain'});
@@ -63,10 +68,14 @@ export class Script {
}
}
- // Save a script FROM THE SCRIPT EDITOR
+ /**
+ * Save a script from the script editor
+ * @param {string} code - The new contents of the script
+ * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
+ */
saveScript(code: string, serverIp: string, otherScripts: Script[]): void {
if (routing.isOn(Page.ScriptEditor)) {
- //Update code and filename
+ // Update code and filename
this.code = code.replace(/^\s+|\s+$/g, '');
const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement;
@@ -75,18 +84,16 @@ export class Script {
return;
}
this.filename = filenameElem!.value;
-
- // Server
this.server = serverIp;
-
- //Calculate/update ram usage, execution time, etc.
this.updateRamUsage(otherScripts);
-
this.module = "";
}
}
- // Updates the script's RAM usage based on its code
+ /**
+ * Calculates and updates the script's RAM usage based on its code
+ * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
+ */
async updateRamUsage(otherScripts: Script[]) {
var res = await calculateRamUsage(this.code, otherScripts);
if (res > 0) {
diff --git a/src/SourceFile.js b/src/SourceFile.js
deleted file mode 100644
index 7c9219562..000000000
--- a/src/SourceFile.js
+++ /dev/null
@@ -1,256 +0,0 @@
-import { Player } from "./Player";
-import { BitNodes } from "./BitNode/BitNode";
-
-// Each SourceFile corresponds to a BitNode with the same number
-function SourceFile(number, info="") {
- var bitnodeKey = "BitNode" + number;
- var bitnode = BitNodes[bitnodeKey];
- if (bitnode == null) {
- throw new Error("Invalid Bit Node for this Source File");
- }
-
- this.n = number;
- this.name = "Source-File " + number + ": " + bitnode.name;
- this.lvl = 1;
- this.info = info;
- this.owned = false;
-}
-
-let SourceFiles = {};
-function initSourceFiles() {
- SourceFiles = {};
- SourceFiles["SourceFile1"] = new SourceFile(1, "This Source-File lets the player start with 32GB of RAM on his/her " +
- "home computer. It also increases all of the player's multipliers by:
" +
- "Level 1: 16% " +
- "Level 2: 24% " +
- "Level 3: 28%");
- SourceFiles["SourceFile2"] = new SourceFile(2, "This Source-File allows you to form gangs in other BitNodes " +
- "once your karma decreases to a certain value. It also increases the player's " +
- "crime success rate, crime money, and charisma multipliers by:
" +
- "Level 1: 24% " +
- "Level 2: 36% " +
- "Level 3: 42%");
- SourceFiles["SourceFile3"] = new SourceFile(3,"This Source-File lets you create corporations on other BitNodes (although " +
- "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by: " +
- "Level 1: 8% " +
- "Level 2: 12% " +
- "Level 3: 14%");
- SourceFiles["SourceFile4"] = new SourceFile(4, "This Source-File lets you access and use the Singularity Functions in every BitNode. Every " +
- "level of this Source-File opens up more of the Singularity Functions you can use.");
- SourceFiles["SourceFile5"] = new SourceFile(5, "This Source-File grants a special new stat called Intelligence. Intelligence " +
- "is unique because it is permanent and persistent (it never gets reset back to 1). However, " +
- "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't " +
- "know when you gain experience and how much). Higher Intelligence levels will boost your production " +
- "for many actions in the game. In addition, this Source-File will unlock the getBitNodeMultipliers() " +
- "Netscript function, and will raise all of your hacking-related multipliers by:
" +
- "Level 1: 8% " +
- "Level 2: 12% " +
- "Level 3: 14%");
- SourceFiles["SourceFile6"] = new SourceFile(6, "This Source-File allows you to access the NSA's Bladeburner Division in other " +
- "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:
" +
- "Level 1: 8% " +
- "Level 2: 12% " +
- "Level 3: 14%");
- SourceFiles["SourceFile7"] = new SourceFile(7, "This Source-File allows you to access the Bladeburner Netscript API in other " +
- "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:
" +
- "Level 1: 8% " +
- "Level 2: 12% " +
- "Level 3: 14%");
- SourceFiles["SourceFile8"] = new SourceFile(8, "This Source-File grants the following benefits:
" +
- "Level 1: Permanent access to WSE and TIX API " +
- "Level 2: Ability to short stocks in other BitNodes " +
- "Level 3: Ability to use limit/stop orders in other BitNodes
" +
- "This Source-File also increases your hacking growth multipliers by: " +
- " Level 1: 12% Level 2: 18% Level 3: 21%");
- SourceFiles["SourceFile9"] = new SourceFile(9, "This Source-File grants the following benefits:
" +
- "Level 1: Permanently unlocks the Hacknet Server in other BitNodes " +
- "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode " +
- "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
" +
- "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " +
- "when installing Augmentations)");
- SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " +
- "Source-File also grants you a Duplicate Sleeve");
- SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " +
- "at that company by 1% per favor (rather than just the reputation gain). This Source-File also " +
- " increases the player's company salary and reputation gain multipliers by:
" +
- "Level 1: 32% " +
- "Level 2: 48% " +
- "Level 3: 56% ");
- SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File increases all your multipliers by 1% per level. This effect is multiplicative with itself. " +
- "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)");
-}
-
-// Takes in a PlayerOwnedSourceFile as the "srcFile" argument
-function applySourceFile(srcFile) {
- var srcFileKey = "SourceFile" + srcFile.n;
- var sourceFileObject = SourceFiles[srcFileKey];
- if (sourceFileObject == null) {
- console.log("ERROR: Invalid source file number: " + srcFile.n);
- return;
- }
-
- switch(srcFile.n) {
- case 1: // The Source Genesis
- var mult = 0;
- for (var i = 0; i < srcFile.lvl; ++i) {
- mult += (16 / (Math.pow(2, i)));
- }
- var incMult = 1 + (mult / 100);
- var decMult = 1 - (mult / 100);
- Player.hacking_chance_mult *= incMult;
- Player.hacking_speed_mult *= incMult;
- Player.hacking_money_mult *= incMult;
- Player.hacking_grow_mult *= incMult;
- Player.hacking_mult *= incMult;
- Player.strength_mult *= incMult;
- Player.defense_mult *= incMult;
- Player.dexterity_mult *= incMult;
- Player.agility_mult *= incMult;
- Player.charisma_mult *= incMult;
- Player.hacking_exp_mult *= incMult;
- Player.strength_exp_mult *= incMult;
- Player.defense_exp_mult *= incMult;
- Player.dexterity_exp_mult *= incMult;
- Player.agility_exp_mult *= incMult;
- Player.charisma_exp_mult *= incMult;
- Player.company_rep_mult *= incMult;
- Player.faction_rep_mult *= incMult;
- Player.crime_money_mult *= incMult;
- Player.crime_success_mult *= incMult;
- Player.hacknet_node_money_mult *= incMult;
- Player.hacknet_node_purchase_cost_mult *= decMult;
- Player.hacknet_node_ram_cost_mult *= decMult;
- Player.hacknet_node_core_cost_mult *= decMult;
- Player.hacknet_node_level_cost_mult *= decMult;
- Player.work_money_mult *= incMult;
- break;
- case 2: // Rise of the Underworld
- var mult = 0;
- for (var i = 0; i < srcFile.lvl; ++i) {
- mult += (24 / (Math.pow(2, i)));
- }
- var incMult = 1 + (mult / 100);
- Player.crime_money_mult *= incMult;
- Player.crime_success_mult *= incMult;
- Player.charisma_mult *= incMult;
- break;
- case 3: // Corporatocracy
- var mult = 0;
- for (var i = 0; i < srcFile.lvl; ++i) {
- mult += (8 / (Math.pow(2, i)));
- }
- var incMult = 1 + (mult / 100);
- Player.charisma_mult *= incMult;
- Player.work_money_mult *= incMult;
- break;
- case 4: // The Singularity
- // No effects, just gives access to Singularity functions
- break;
- case 5: // Artificial Intelligence
- var mult = 0;
- for (var i = 0; i < srcFile.lvl; ++i) {
- mult += (8 / (Math.pow(2, i)));
- }
- var incMult = 1 + (mult / 100);
- Player.hacking_chance_mult *= incMult;
- Player.hacking_speed_mult *= incMult;
- Player.hacking_money_mult *= incMult;
- Player.hacking_grow_mult *= incMult;
- Player.hacking_mult *= incMult;
- Player.hacking_exp_mult *= incMult;
- break;
- case 6: // Bladeburner
- var mult = 0;
- for (var i = 0; i < srcFile.lvl; ++i) {
- mult += (8 / (Math.pow(2, i)));
- }
- var incMult = 1 + (mult / 100);
- Player.strength_exp_mult *= incMult;
- Player.defense_exp_mult *= incMult;
- Player.dexterity_exp_mult *= incMult;
- Player.agility_exp_mult *= incMult;
- Player.strength_mult *= incMult;
- Player.defense_mult *= incMult;
- Player.dexterity_mult *= incMult;
- Player.agility_mult *= incMult;
- break;
- case 7: // Bladeburner 2079
- var mult = 0;
- for (var i = 0; i < srcFile.lvl; ++i) {
- mult += (8 / (Math.pow(2, i)));
- }
- var incMult = 1 + (mult / 100);
- Player.bladeburner_max_stamina_mult *= incMult;
- Player.bladeburner_stamina_gain_mult *= incMult;
- Player.bladeburner_analysis_mult *= incMult;
- Player.bladeburner_success_chance_mult *= incMult;
- break;
- case 8: // Ghost of Wall Street
- var mult = 0;
- for (var i = 0; i < srcFile.lvl; ++i) {
- mult += (12 / (Math.pow(2, i)));
- }
- var incMult = 1 + (mult / 100);
- Player.hacking_grow_mult *= incMult;
- break;
- case 9: // Hacktocracy
- // This has non-multiplier effects
- break;
- case 10: // Digital Carbon
- // No effects, just grants sleeves
- break;
- case 11: // The Big Crash
- var mult = 0;
- for (var i = 0; i < srcFile.lvl; ++i) {
- mult += (32 / (Math.pow(2, i)));
- }
- var incMult = 1 + (mult / 100);
- Player.work_money_mult *= incMult;
- Player.company_rep_mult *= incMult;
- break;
- case 12: // The Recursion
- var inc = Math.pow(1.01, srcFile.lvl);
- var dec = Math.pow(0.99, srcFile.lvl);
-
- Player.hacking_chance_mult *= inc;
- Player.hacking_speed_mult *= inc;
- Player.hacking_money_mult *= inc;
- Player.hacking_grow_mult *= inc;
- Player.hacking_mult *= inc;
-
- Player.strength_mult *= inc;
- Player.defense_mult *= inc;
- Player.dexterity_mult *= inc;
- Player.agility_mult *= inc;
- Player.charisma_mult *= inc;
-
- Player.hacking_exp_mult *= inc;
- Player.strength_exp_mult *= inc;
- Player.defense_exp_mult *= inc;
- Player.dexterity_exp_mult *= inc;
- Player.agility_exp_mult *= inc;
- Player.charisma_exp_mult *= inc;
-
- Player.company_rep_mult *= inc;
- Player.faction_rep_mult *= inc;
-
- Player.crime_money_mult *= inc;
- Player.crime_success_mult *= inc;
-
- Player.hacknet_node_money_mult *= inc;
- Player.hacknet_node_purchase_cost_mult *= dec;
- Player.hacknet_node_ram_cost_mult *= dec;
- Player.hacknet_node_core_cost_mult *= dec;
- Player.hacknet_node_level_cost_mult *= dec;
-
- Player.work_money_mult *= inc;
- break;
- default:
- console.log("ERROR: Invalid source file number: " + srcFile.n);
- break;
- }
-
- sourceFileObject.owned = true;
-}
-
-export {SourceFiles, applySourceFile, initSourceFiles};
diff --git a/src/SourceFile/SourceFile.ts b/src/SourceFile/SourceFile.ts
new file mode 100644
index 000000000..96290507a
--- /dev/null
+++ b/src/SourceFile/SourceFile.ts
@@ -0,0 +1,21 @@
+import { BitNodes } from "../BitNode/BitNode";
+
+export class SourceFile {
+ info: string;
+ lvl: number = 1;
+ n: number;
+ name: string;
+ owned: boolean = false;
+
+ constructor(number: number, info: string="") {
+ const bitnodeKey = "BitNode" + number;
+ const bitnode = BitNodes[bitnodeKey];
+ if (bitnode == null) {
+ throw new Error("Invalid Bit Node for this Source File");
+ }
+
+ this.n = number;
+ this.name = `Source-File ${number}: ${bitnode.name}`
+ this.info = info;
+ }
+}
diff --git a/src/SourceFile/SourceFiles.ts b/src/SourceFile/SourceFiles.ts
new file mode 100644
index 000000000..bc35dcba8
--- /dev/null
+++ b/src/SourceFile/SourceFiles.ts
@@ -0,0 +1,64 @@
+import { SourceFile } from "./SourceFile";
+import { IMap } from "../types";
+
+export const SourceFiles: IMap = {};
+
+SourceFiles["SourceFile1"] = new SourceFile(1, "This Source-File lets the player start with 32GB of RAM on his/her " +
+ "home computer. It also increases all of the player's multipliers by:
" +
+ "Level 1: 16% " +
+ "Level 2: 24% " +
+ "Level 3: 28%");
+SourceFiles["SourceFile2"] = new SourceFile(2, "This Source-File allows you to form gangs in other BitNodes " +
+ "once your karma decreases to a certain value. It also increases the player's " +
+ "crime success rate, crime money, and charisma multipliers by:
" +
+ "Level 1: 24% " +
+ "Level 2: 36% " +
+ "Level 3: 42%");
+SourceFiles["SourceFile3"] = new SourceFile(3,"This Source-File lets you create corporations on other BitNodes (although " +
+ "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by: " +
+ "Level 1: 8% " +
+ "Level 2: 12% " +
+ "Level 3: 14%");
+SourceFiles["SourceFile4"] = new SourceFile(4, "This Source-File lets you access and use the Singularity Functions in every BitNode. Every " +
+ "level of this Source-File opens up more of the Singularity Functions you can use.");
+SourceFiles["SourceFile5"] = new SourceFile(5, "This Source-File grants a special new stat called Intelligence. Intelligence " +
+ "is unique because it is permanent and persistent (it never gets reset back to 1). However, " +
+ "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't " +
+ "know when you gain experience and how much). Higher Intelligence levels will boost your production " +
+ "for many actions in the game. In addition, this Source-File will unlock the getBitNodeMultipliers() " +
+ "Netscript function, and will raise all of your hacking-related multipliers by:
" +
+ "Level 1: 8% " +
+ "Level 2: 12% " +
+ "Level 3: 14%");
+SourceFiles["SourceFile6"] = new SourceFile(6, "This Source-File allows you to access the NSA's Bladeburner Division in other " +
+ "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:
" +
+ "Level 1: 8% " +
+ "Level 2: 12% " +
+ "Level 3: 14%");
+SourceFiles["SourceFile7"] = new SourceFile(7, "This Source-File allows you to access the Bladeburner Netscript API in other " +
+ "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:
" +
+ "Level 1: 8% " +
+ "Level 2: 12% " +
+ "Level 3: 14%");
+SourceFiles["SourceFile8"] = new SourceFile(8, "This Source-File grants the following benefits:
" +
+ "Level 1: Permanent access to WSE and TIX API " +
+ "Level 2: Ability to short stocks in other BitNodes " +
+ "Level 3: Ability to use limit/stop orders in other BitNodes
" +
+ "This Source-File also increases your hacking growth multipliers by: " +
+ " Level 1: 12% Level 2: 18% Level 3: 21%");
+SourceFiles["SourceFile9"] = new SourceFile(9, "This Source-File grants the following benefits:
" +
+ "Level 1: Permanently unlocks the Hacknet Server in other BitNodes " +
+ "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode " +
+ "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
" +
+ "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " +
+ "when installing Augmentations)");
+SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " +
+ "Source-File also grants you a Duplicate Sleeve");
+SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " +
+ "at that company by 1% per favor (rather than just the reputation gain). This Source-File also " +
+ " increases the player's company salary and reputation gain multipliers by:
" +
+ "Level 1: 32% " +
+ "Level 2: 48% " +
+ "Level 3: 56% ");
+SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File increases all your multipliers by 1% per level. This effect is multiplicative with itself. " +
+ "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)");
diff --git a/src/SourceFile/applySourceFile.ts b/src/SourceFile/applySourceFile.ts
new file mode 100644
index 000000000..e38b014ec
--- /dev/null
+++ b/src/SourceFile/applySourceFile.ts
@@ -0,0 +1,176 @@
+import { PlayerOwnedSourceFile } from "./PlayerOwnedSourceFile";
+import { SourceFiles } from "./SourceFiles";
+
+import { Player } from "../Player";
+
+export function applySourceFile(srcFile: PlayerOwnedSourceFile) {
+ const srcFileKey = "SourceFile" + srcFile.n;
+ const sourceFileObject = SourceFiles[srcFileKey];
+ if (sourceFileObject == null) {
+ console.error(`Invalid source file number: ${srcFile.n}`);
+ return;
+ }
+
+ switch (srcFile.n) {
+ case 1: // The Source Genesis
+ var mult = 0;
+ for (var i = 0; i < srcFile.lvl; ++i) {
+ mult += (16 / (Math.pow(2, i)));
+ }
+ var incMult = 1 + (mult / 100);
+ var decMult = 1 - (mult / 100);
+ Player.hacking_chance_mult *= incMult;
+ Player.hacking_speed_mult *= incMult;
+ Player.hacking_money_mult *= incMult;
+ Player.hacking_grow_mult *= incMult;
+ Player.hacking_mult *= incMult;
+ Player.strength_mult *= incMult;
+ Player.defense_mult *= incMult;
+ Player.dexterity_mult *= incMult;
+ Player.agility_mult *= incMult;
+ Player.charisma_mult *= incMult;
+ Player.hacking_exp_mult *= incMult;
+ Player.strength_exp_mult *= incMult;
+ Player.defense_exp_mult *= incMult;
+ Player.dexterity_exp_mult *= incMult;
+ Player.agility_exp_mult *= incMult;
+ Player.charisma_exp_mult *= incMult;
+ Player.company_rep_mult *= incMult;
+ Player.faction_rep_mult *= incMult;
+ Player.crime_money_mult *= incMult;
+ Player.crime_success_mult *= incMult;
+ Player.hacknet_node_money_mult *= incMult;
+ Player.hacknet_node_purchase_cost_mult *= decMult;
+ Player.hacknet_node_ram_cost_mult *= decMult;
+ Player.hacknet_node_core_cost_mult *= decMult;
+ Player.hacknet_node_level_cost_mult *= decMult;
+ Player.work_money_mult *= incMult;
+ break;
+ case 2: // Rise of the Underworld
+ var mult = 0;
+ for (var i = 0; i < srcFile.lvl; ++i) {
+ mult += (24 / (Math.pow(2, i)));
+ }
+ var incMult = 1 + (mult / 100);
+ Player.crime_money_mult *= incMult;
+ Player.crime_success_mult *= incMult;
+ Player.charisma_mult *= incMult;
+ break;
+ case 3: // Corporatocracy
+ var mult = 0;
+ for (var i = 0; i < srcFile.lvl; ++i) {
+ mult += (8 / (Math.pow(2, i)));
+ }
+ var incMult = 1 + (mult / 100);
+ Player.charisma_mult *= incMult;
+ Player.work_money_mult *= incMult;
+ break;
+ case 4: // The Singularity
+ // No effects, just gives access to Singularity functions
+ break;
+ case 5: // Artificial Intelligence
+ var mult = 0;
+ for (var i = 0; i < srcFile.lvl; ++i) {
+ mult += (8 / (Math.pow(2, i)));
+ }
+ var incMult = 1 + (mult / 100);
+ Player.hacking_chance_mult *= incMult;
+ Player.hacking_speed_mult *= incMult;
+ Player.hacking_money_mult *= incMult;
+ Player.hacking_grow_mult *= incMult;
+ Player.hacking_mult *= incMult;
+ Player.hacking_exp_mult *= incMult;
+ break;
+ case 6: // Bladeburner
+ var mult = 0;
+ for (var i = 0; i < srcFile.lvl; ++i) {
+ mult += (8 / (Math.pow(2, i)));
+ }
+ var incMult = 1 + (mult / 100);
+ Player.strength_exp_mult *= incMult;
+ Player.defense_exp_mult *= incMult;
+ Player.dexterity_exp_mult *= incMult;
+ Player.agility_exp_mult *= incMult;
+ Player.strength_mult *= incMult;
+ Player.defense_mult *= incMult;
+ Player.dexterity_mult *= incMult;
+ Player.agility_mult *= incMult;
+ break;
+ case 7: // Bladeburner 2079
+ var mult = 0;
+ for (var i = 0; i < srcFile.lvl; ++i) {
+ mult += (8 / (Math.pow(2, i)));
+ }
+ var incMult = 1 + (mult / 100);
+ Player.bladeburner_max_stamina_mult *= incMult;
+ Player.bladeburner_stamina_gain_mult *= incMult;
+ Player.bladeburner_analysis_mult *= incMult;
+ Player.bladeburner_success_chance_mult *= incMult;
+ break;
+ case 8: // Ghost of Wall Street
+ var mult = 0;
+ for (var i = 0; i < srcFile.lvl; ++i) {
+ mult += (12 / (Math.pow(2, i)));
+ }
+ var incMult = 1 + (mult / 100);
+ Player.hacking_grow_mult *= incMult;
+ break;
+ case 9: // Hacktocracy
+ // This has non-multiplier effects
+ break;
+ case 10: // Digital Carbon
+ // No effects, just grants sleeves
+ break;
+ case 11: // The Big Crash
+ var mult = 0;
+ for (var i = 0; i < srcFile.lvl; ++i) {
+ mult += (32 / (Math.pow(2, i)));
+ }
+ var incMult = 1 + (mult / 100);
+ Player.work_money_mult *= incMult;
+ Player.company_rep_mult *= incMult;
+ break;
+ case 12: // The Recursion
+ var inc = Math.pow(1.01, srcFile.lvl);
+ var dec = Math.pow(0.99, srcFile.lvl);
+
+ Player.hacking_chance_mult *= inc;
+ Player.hacking_speed_mult *= inc;
+ Player.hacking_money_mult *= inc;
+ Player.hacking_grow_mult *= inc;
+ Player.hacking_mult *= inc;
+
+ Player.strength_mult *= inc;
+ Player.defense_mult *= inc;
+ Player.dexterity_mult *= inc;
+ Player.agility_mult *= inc;
+ Player.charisma_mult *= inc;
+
+ Player.hacking_exp_mult *= inc;
+ Player.strength_exp_mult *= inc;
+ Player.defense_exp_mult *= inc;
+ Player.dexterity_exp_mult *= inc;
+ Player.agility_exp_mult *= inc;
+ Player.charisma_exp_mult *= inc;
+
+ Player.company_rep_mult *= inc;
+ Player.faction_rep_mult *= inc;
+
+ Player.crime_money_mult *= inc;
+ Player.crime_success_mult *= inc;
+
+ Player.hacknet_node_money_mult *= inc;
+ Player.hacknet_node_purchase_cost_mult *= dec;
+ Player.hacknet_node_ram_cost_mult *= dec;
+ Player.hacknet_node_core_cost_mult *= dec;
+ Player.hacknet_node_level_cost_mult *= dec;
+
+ Player.work_money_mult *= inc;
+ break;
+ default:
+ console.log("ERROR: Invalid source file number: " + srcFile.n);
+ break;
+ }
+
+ sourceFileObject.owned = true;
+}
diff --git a/src/Terminal.js b/src/Terminal.js
index b88832954..12e150742 100644
--- a/src/Terminal.js
+++ b/src/Terminal.js
@@ -53,7 +53,8 @@ import {
import { showLiterature } from "./Literature";
import { Message } from "./Message/Message";
import { showMessage } from "./Message/MessageHelpers";
-import { killWorkerScript, addWorkerScript } from "./NetscriptWorker";
+import { addWorkerScript } from "./NetscriptWorker";
+import { killWorkerScript } from "./Netscript/killWorkerScript";
import { Player } from "./Player";
import { hackWorldDaemon } from "./RedPill";
import { RunningScript } from "./Script/RunningScript";
diff --git a/src/engine.jsx b/src/engine.jsx
index e86bbcdfb..20f2ff35b 100644
--- a/src/engine.jsx
+++ b/src/engine.jsx
@@ -1,29 +1,26 @@
+/**
+ * Game engine. Handles the main game loop as well as the main UI pages
+ *
+ * TODO: Separate UI functionality into its own component
+ */
import {
- formatNumber,
convertTimeMsToTimeElapsedString,
replaceAt
} from "../utils/StringHelperFunctions";
-import { loxBoxCreate, logBoxUpdateText, logBoxOpened } from "../utils/LogBox";
-import { updateActiveScriptsItems } from "./ActiveScriptsUI";
+import { logBoxUpdateText, logBoxOpened } from "../utils/LogBox";
import { Augmentations } from "./Augmentation/Augmentations";
import {
- installAugmentations,
initAugmentations,
displayAugmentationsContent,
- PlayerOwnedAugmentation
} from "./Augmentation/AugmentationHelpers";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
-
import {
- BitNodes,
- initBitNodes,
initBitNodeMultipliers
} from "./BitNode/BitNode";
import { Bladeburner } from "./Bladeburner";
import { CharacterOverviewComponent } from "./ui/React/CharacterOverview";
import { cinematicTextFlag } from "./CinematicText";
import { generateRandomContract } from "./CodingContractGenerator";
-import { CompanyPositions } from "./Company/CompanyPositions";
import { initCompanies } from "./Company/Companies";
import { Corporation } from "./Corporation/Corporation";
import { CONSTANTS } from "./Constants";
@@ -48,46 +45,36 @@ import { LocationName } from "./Locations/data/LocationNames";
import { LocationRoot } from "./Locations/ui/Root";
import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers";
import { inMission, currMission } from "./Missions";
+import { workerScripts } from "./Netscript/WorkerScripts";
import {
loadAllRunningScripts,
- runScriptsLoop,
updateOnlineScriptTimes,
} from "./NetscriptWorker";
import { Player } from "./Player";
-import { prestigeAugmentation, prestigeSourceFile } from "./Prestige";
-import { Programs } from "./Programs/Programs";
+import { prestigeAugmentation } from "./Prestige";
import {
displayCreateProgramContent,
getNumAvailableCreateProgram,
initCreateProgramButtons
} from "./Programs/ProgramHelpers";
-import { redPillFlag, hackWorldDaemon } from "./RedPill";
+import { redPillFlag } from "./RedPill";
import { saveObject, loadGame } from "./SaveObject";
import {
getCurrentEditor,
scriptEditorInit,
updateScriptEditorContent
} from "./Script/ScriptHelpers";
-import { AllServers, initForeignServers } from "./Server/AllServers";
-
-import { Server } from "./Server/Server";
+import { initForeignServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings";
-import { initSourceFiles, SourceFiles } from "./SourceFile";
import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
+import { initSpecialServerIps } from "./Server/SpecialServerIps";
import {
- SpecialServerIps,
- initSpecialServerIps
-} from "./Server/SpecialServerIps";
-import {
- StockMarket,
- SymbolToStockMap,
initSymbolToStockMap,
stockMarketCycle,
processStockPrices,
displayStockMarketContent
} from "./StockMarket/StockMarket";
import { Terminal, postNetburnerText } from "./Terminal";
-
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
import {
clearSleevesPage,
@@ -104,14 +91,14 @@ import { displayCharacterInfo } from "./ui/displayCharacterInfo";
import { Page, routing } from "./ui/navigationTracking";
import { numeralWrapper } from "./ui/numeralFormat";
import { setSettingsLabels } from "./ui/setSettingsLabels";
+
+import { ActiveScriptsRoot } from "./ui/ActiveScripts/Root";
import { initializeMainMenuHeaders } from "./ui/MainMenu/Headers";
import { initializeMainMenuLinks, MainMenuLinks } from "./ui/MainMenu/Links";
import { dialogBoxCreate } from "../utils/DialogBox";
import { gameOptionsBoxClose, gameOptionsBoxOpen } from "../utils/GameOptions";
-import { getRandomInt } from "../utils/helpers/getRandomInt";
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
-import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { createElement } from "../utils/uiHelpers/createElement";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { removeLoadingScreen } from "../utils/uiHelpers/removeLoadingScreen";
@@ -278,7 +265,10 @@ const Engine = {
Engine.hideAllContent();
Engine.Display.activeScriptsContent.style.display = "block";
routing.navigateTo(Page.ActiveScripts);
- updateActiveScriptsItems();
+ ReactDOM.render(
+ ,
+ Engine.Display.activeScriptsContent
+ )
MainMenuLinks.ActiveScripts.classList.add("active");
},
@@ -315,8 +305,8 @@ const Engine = {
loadAugmentationsContent: function() {
Engine.hideAllContent();
Engine.Display.augmentationsContent.style.display = "block";
- displayAugmentationsContent(Engine.Display.augmentationsContent);
routing.navigateTo(Page.Augmentations);
+ displayAugmentationsContent(Engine.Display.augmentationsContent);
MainMenuLinks.Augmentations.classList.add("active");
},
@@ -490,16 +480,26 @@ const Engine = {
Engine.Display.terminalContent.style.display = "none";
Engine.Display.characterContent.style.display = "none";
Engine.Display.scriptEditorContent.style.display = "none";
+
Engine.Display.activeScriptsContent.style.display = "none";
+ ReactDOM.unmountComponentAtNode(Engine.Display.activeScriptsContent);
+
clearHacknetNodesUI();
Engine.Display.createProgramContent.style.display = "none";
+
Engine.Display.factionsContent.style.display = "none";
- ReactDOM.unmountComponentAtNode(Engine.Display.factionContent);
+
Engine.Display.factionContent.style.display = "none";
+ ReactDOM.unmountComponentAtNode(Engine.Display.factionContent);
+
Engine.Display.augmentationsContent.style.display = "none";
+ ReactDOM.unmountComponentAtNode(Engine.Display.augmentationsContent);
+
Engine.Display.tutorialContent.style.display = "none";
+
Engine.Display.locationContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.locationContent);
+
Engine.Display.workInProgressContent.style.display = "none";
Engine.Display.redPillContent.style.display = "none";
Engine.Display.cinematicTextContent.style.display = "none";
@@ -814,13 +814,14 @@ const Engine = {
}
if (Engine.Counters.updateActiveScriptsDisplay <= 0) {
- // Always update, but make the interval longer if the page isn't active
- updateActiveScriptsItems();
if (routing.isOn(Page.ActiveScripts)) {
- Engine.Counters.updateActiveScriptsDisplay = 5;
- } else {
- Engine.Counters.updateActiveScriptsDisplay = 10;
+ ReactDOM.render(
+ ,
+ Engine.Display.activeScriptsContent
+ )
}
+
+ Engine.Counters.updateActiveScriptsDisplay = 5;
}
if (Engine.Counters.updateDisplays <= 0) {
@@ -1043,9 +1044,7 @@ const Engine = {
// Load game from save or create new game
if (loadGame(saveString)) {
- initBitNodes();
initBitNodeMultipliers(Player);
- initSourceFiles();
Engine.setDisplayElements(); // Sets variables for important DOM elements
Engine.init(); // Initialize buttons, work, etc.
initAugmentations(); // Also calls Player.reapplyAllAugmentations()
@@ -1166,9 +1165,7 @@ const Engine = {
} else {
// No save found, start new game
console.log("Initializing new game");
- initBitNodes();
initBitNodeMultipliers(Player);
- initSourceFiles();
initSpecialServerIps();
Engine.setDisplayElements(); // Sets variables for important DOM elements
Engine.start(); // Run main game loop and Scripts loop
@@ -1531,9 +1528,6 @@ const Engine = {
start: function() {
// Run main loop
Engine.idleTimer();
-
- // Script-processing loop
- runScriptsLoop();
}
};
diff --git a/src/engineStyle.js b/src/engineStyle.js
index df9dd7250..f9895a77a 100644
--- a/src/engineStyle.js
+++ b/src/engineStyle.js
@@ -9,8 +9,10 @@ import "../css/characteroverview.scss";
import "../css/terminal.scss";
import "../css/scripteditor.scss";
import "../css/codemirror-overrides.scss";
+import "../css/activescripts.scss";
import "../css/hacknetnodes.scss";
import "../css/menupages.scss";
+import "../css/augmentations.scss";
import "../css/redpill.scss";
import "../css/stockmarket.scss";
import "../css/workinprogress.scss";
diff --git a/src/ui/ActiveScripts/Root.tsx b/src/ui/ActiveScripts/Root.tsx
new file mode 100644
index 000000000..2dd6e78d0
--- /dev/null
+++ b/src/ui/ActiveScripts/Root.tsx
@@ -0,0 +1,38 @@
+/**
+ * Root React Component for the "Active Scripts" UI page. This page displays
+ * and provides information about all of the player's scripts that are currently running
+ */
+import * as React from "react";
+
+import { ScriptProduction } from "./ScriptProduction";
+import { ServerAccordions } from "./ServerAccordions";
+
+import { WorkerScript } from "../../Netscript/WorkerScript";
+import { IPlayer } from "../../PersonObjects/IPlayer";
+
+type IProps = {
+ p: IPlayer;
+ workerScripts: WorkerScript[];
+}
+
+export class ActiveScriptsRoot extends React.Component {
+ constructor(props: IProps) {
+ super(props);
+ }
+
+ render() {
+ return (
+ <>
+
+ This page displays a list of all of your scripts that are currently
+ running across every machine. It also provides information about each
+ script's production. The scripts are categorized by the hostname of
+ the servers on which they are running.
+
+
+
+
+ >
+ )
+ }
+}
diff --git a/src/ui/ActiveScripts/ScriptProduction.tsx b/src/ui/ActiveScripts/ScriptProduction.tsx
new file mode 100644
index 000000000..66da02a01
--- /dev/null
+++ b/src/ui/ActiveScripts/ScriptProduction.tsx
@@ -0,0 +1,46 @@
+/**
+ * React Component for displaying the total production and production rate
+ * of scripts on the 'Active Scripts' UI page
+ */
+import * as React from "react";
+
+import { numeralWrapper } from "../numeralFormat";
+
+import { WorkerScript } from "../../Netscript/WorkerScript";
+import { IPlayer } from "../../PersonObjects/IPlayer";
+
+type IProps = {
+ p: IPlayer;
+ workerScripts: WorkerScript[];
+}
+
+export function ScriptProduction(props: IProps): React.ReactElement {
+ const prodRateSinceLastAug = props.p.scriptProdSinceLastAug / (props.p.playtimeSinceLastAug / 1000);
+
+ let onlineProduction = 0;
+ for (const ws of props.workerScripts) {
+ onlineProduction += (ws.scriptRef.onlineMoneyMade / ws.scriptRef.onlineRunningTime);
+ }
+
+ return (
+
+ Total online production of Active scripts:
+
+
+ {numeralWrapper.formatMoney(onlineProduction)}
+ / sec
+
+
+ Total online production since last Aug installation:
+
+ {numeralWrapper.formatMoney(props.p.scriptProdSinceLastAug)}
+
+
+ (
+
+ {numeralWrapper.formatMoney(prodRateSinceLastAug)}
+ / sec
+ )
+
+ )
+}
diff --git a/src/ui/ActiveScripts/ServerAccordion.tsx b/src/ui/ActiveScripts/ServerAccordion.tsx
new file mode 100644
index 000000000..9a0d1c57f
--- /dev/null
+++ b/src/ui/ActiveScripts/ServerAccordion.tsx
@@ -0,0 +1,49 @@
+/**
+ * React Component for rendering the Accordion element for a single
+ * server in the 'Active Scripts' UI page
+ */
+import * as React from "react";
+
+import { WorkerScriptAccordion } from "./WorkerScriptAccordion";
+import { Accordion } from "../React/Accordion";
+
+import { BaseServer } from "../../Server/BaseServer";
+import { WorkerScript } from "../../Netscript/WorkerScript";
+
+import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
+
+type IProps = {
+ server: BaseServer;
+ workerScripts: WorkerScript[];
+}
+
+export function ServerAccordion(props: IProps): React.ReactElement {
+ const server = props.server;
+
+ // Accordion's header text
+ // TODO: calculate the longest hostname length rather than hard coding it
+ const longestHostnameLength = 18;
+ const paddedName = `${server.hostname}${" ".repeat(longestHostnameLength)}`.slice(0, Math.max(server.hostname.length, longestHostnameLength));
+ const barOptions = {
+ progress: server.ramUsed / server.maxRam,
+ totalTicks: 30
+ };
+ const headerTxt = `${paddedName} ${createProgressBarText(barOptions)}`;
+
+ const scripts = props.workerScripts.map((ws) => {
+ return (
+
+ )
+ });
+
+ return (
+ {headerTxt}
+ }
+ panelContent={
+
{scripts}
+ }
+ />
+ )
+}
diff --git a/src/ui/ActiveScripts/ServerAccordions.tsx b/src/ui/ActiveScripts/ServerAccordions.tsx
new file mode 100644
index 000000000..554d69317
--- /dev/null
+++ b/src/ui/ActiveScripts/ServerAccordions.tsx
@@ -0,0 +1,109 @@
+/**
+ * React Component for rendering the Accordion elements for all servers
+ * on which scripts are running
+ */
+import * as React from "react";
+
+import { ServerAccordion } from "./ServerAccordion";
+
+import { WorkerScript } from "../../Netscript/WorkerScript";
+import { WorkerScriptStartStopEventEmitter } from "../../Netscript/WorkerScriptStartStopEventEmitter";
+import { getServer } from "../../Server/ServerHelpers";
+import { BaseServer } from "../../Server/BaseServer";
+
+// Map of server hostname -> all workerscripts on that server for all active scripts
+interface IServerData {
+ server: BaseServer;
+ workerScripts: WorkerScript[];
+}
+
+interface IServerToScriptsMap {
+ [key: string]: IServerData;
+}
+
+type IProps = {
+ workerScripts: WorkerScript[];
+};
+
+type IState = {
+ rerenderFlag: boolean;
+}
+
+
+const subscriberId = "ActiveScriptsUI";
+
+export class ServerAccordions extends React.Component {
+ serverToScriptMap: IServerToScriptsMap = {};
+
+ constructor(props: IProps) {
+ super(props);
+
+ this.state = {
+ rerenderFlag: false,
+ }
+
+ this.updateServerToScriptsMap();
+
+ this.rerender = this.rerender.bind(this);
+ }
+
+ componentDidMount() {
+ WorkerScriptStartStopEventEmitter.addSubscriber({
+ cb: this.rerender,
+ id: subscriberId,
+ })
+ }
+
+ componentWillUnmount() {
+ WorkerScriptStartStopEventEmitter.removeSubscriber(subscriberId);
+ }
+
+ updateServerToScriptsMap(): void {
+ const map: IServerToScriptsMap = {};
+
+ for (const ws of this.props.workerScripts) {
+ const server = getServer(ws.serverIp);
+ if (server == null) {
+ console.warn(`WorkerScript has invalid IP address: ${ws.serverIp}`);
+ continue;
+ }
+
+ if (map[server.hostname] == null) {
+ map[server.hostname] = {
+ server: server,
+ workerScripts: [],
+ };
+ }
+
+ map[server.hostname].workerScripts.push(ws);
+ }
+
+ this.serverToScriptMap = map;
+ }
+
+ rerender() {
+ this.updateServerToScriptsMap();
+ this.setState((prevState) => {
+ return { rerenderFlag: !prevState.rerenderFlag }
+ });
+ }
+
+ render() {
+ const elems = Object.keys(this.serverToScriptMap).map((serverName) => {
+ const data = this.serverToScriptMap[serverName];
+ return (
+
+ )
+ });
+
+ return (
+
+ {elems}
+
+ )
+ }
+}
diff --git a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx
new file mode 100644
index 000000000..bbf522596
--- /dev/null
+++ b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx
@@ -0,0 +1,77 @@
+/**
+ * React Component for displaying a single WorkerScript's info as an
+ * Accordion element
+ */
+import * as React from "react";
+
+import { numeralWrapper } from "../numeralFormat";
+
+import { Accordion } from "../React/Accordion";
+import { AccordionButton } from "../React/AccordionButton";
+
+import { killWorkerScript } from "../../Netscript/killWorkerScript";
+import { WorkerScript } from "../../Netscript/WorkerScript";
+
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { logBoxCreate } from "../../../utils/LogBox";
+import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
+import { arrayToString } from "../../../utils/helpers/arrayToString";
+
+type IProps = {
+ workerScript: WorkerScript;
+}
+
+export function WorkerScriptAccordion(props: IProps): React.ReactElement {
+ const workerScript = props.workerScript;
+ const scriptRef = workerScript.scriptRef;
+
+
+ const logClickHandler = logBoxCreate.bind(null, scriptRef);
+ const killScript = killWorkerScript.bind(null, scriptRef, scriptRef.server);
+
+ function killScriptClickHandler() {
+ killScript();
+ dialogBoxCreate("Killing script");
+ }
+
+ // Calculations for script stats
+ const onlineMps = scriptRef.onlineMoneyMade / scriptRef.onlineRunningTime;
+ const onlineEps = scriptRef.onlineExpGained / scriptRef.onlineRunningTime;
+ const offlineMps = scriptRef.offlineMoneyMade / scriptRef.offlineRunningTime;
+ const offlineEps = scriptRef.offlineExpGained / scriptRef.offlineRunningTime;
+
+ return (
+ {props.workerScript.name}>
+ }
+ panelClass="active-scripts-script-panel"
+ panelContent={
+ <>
+