merge base

This commit is contained in:
phyzical 2022-04-14 21:27:08 +08:00
commit 9c23fc89d1
104 changed files with 1826 additions and 1276 deletions

1
.gitignore vendored

@ -1,4 +1,5 @@
.DS_Store .DS_Store
.history
.vscode .vscode
Changelog.txt Changelog.txt
Netburner.txt Netburner.txt

16
dist/bitburner.d.ts vendored

@ -1773,6 +1773,18 @@ export declare interface Grafting {
*/ */
getAugmentationGraftTime(augName: string): number; getAugmentationGraftTime(augName: string): number;
/**
* Retrieves a list of Augmentations that can be grafted.
* @remarks
* RAM cost: 5 GB
*
* Note that this function returns a list of currently graftable Augmentations,
* based off of the Augmentations that you already own.
*
* @returns An array of graftable Augmentations.
*/
getGraftableAugmentations(): string[];
/** /**
* Begins grafting the named aug. You must be in New Tokyo to use this. * Begins grafting the named aug. You must be in New Tokyo to use this.
* @remarks * @remarks
@ -2911,9 +2923,11 @@ export declare interface NS {
* Returns the security increase that would occur if a grow with this many threads happened. * Returns the security increase that would occur if a grow with this many threads happened.
* *
* @param threads - Amount of threads that will be used. * @param threads - Amount of threads that will be used.
* @param hostname - Optional. Hostname of the target server. The number of threads is limited to the number needed to hack the servers maximum amount of money.
* @param cores - Optional. The number of cores of the server that would run grow.
* @returns The security increase. * @returns The security increase.
*/ */
growthAnalyzeSecurity(threads: number): number; growthAnalyzeSecurity(threads: number, hostname?: string, cores?: number): number;
/** /**
* Suspends the script for n milliseconds. * Suspends the script for n milliseconds.

4
dist/main.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

40
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -66,7 +66,7 @@ documentation_title = '{0} Documentation'.format(project)
# The short X.Y version. # The short X.Y version.
version = '1.6' version = '1.6'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '1.6.3' release = '1.6.4'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

@ -1,453 +0,0 @@
:root {
--dark-text-color: #0c0;
--dark-link-color: #090;
}
body {
color: #000;
}
.wy-nav-content-wrap {
background-color: #000;
}
.wy-nav-content {
background-color: #000;
}
.section {
color: var(--dark-text-color);
}
.rst-content .highlighted {
background: #333;
box-shadow: none;
}
.highlight {
background-color: #17181c;
}
.highlight .nn {
color: var(--dark-text-color);
}
.highlight .nb {
color: #8bb8df;
}
.highlight .kn,
.highlight .kc,
.highlight .k {
color: #41c2ea;
}
.highlight .s1,
.highlight .s2 {
color: #b3e87f;
}
.highlight .nt {
color: #ccb350;
}
.highlight .c1 {
color: #686868;
}
.rst-content div[class^="highlight"] {
border-color: #1a1a1a;
}
.icon,
.icon-home {
color: var(--dark-link-color);
}
.wy-nav-content a,
.wy-nav-content a:visited {
color: var(--dark-link-color) !important;
text-decoration: underline;
}
.btn-neutral {
background-color: #17181c !important;
}
.btn-neutral:hover {
background-color: #101114 !important;
}
.btn-neutral:visited {
color: #c1c1c1 !important;
}
.btn {
box-shadow: none;
}
footer {
color: #bdbdbd;
}
.wy-nav-side {
background-color: #000;
border: 1px solid #333;
}
.wy-menu-vertical > a {
color: var(--dark-text-color);
}
.wy-menu-vertical li.current {
background-color: #000;
}
.wy-menu-vertical li.current > a,
.wy-menu-vertical li.on a {
background-color: #000;
color: var(--dark-text-color);
}
.wy-menu-vertical li.toctree-l1.current > a,
.wy-menu-vertical li.current a {
border-color: #000;
}
.wy-menu-vertical header,
.wy-menu-vertical p.caption {
color: var(--dark-text-color);
}
html.writer-html4 .rst-content dl:not(.docutils) > dt,
html.writer-html5
.rst-content
dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)
> dt {
background-color: #333;
}
.wy-menu-vertical li.current a {
color: #090;
}
.wy-menu-vertical a {
color: var(--dark-text-color);
}
.wy-menu-vertical li.current a:hover {
background-color: #222;
}
.wy-menu-vertical a:hover,
.wy-menu-vertical li.current > a:hover,
.wy-menu-vertical li.on a:hover {
background-color: #000;
color: var(--dark-text-color);
}
.wy-menu-vertical li.toctree-l2.current > a,
.wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a {
background-color: #000;
}
.wy-side-nav-search {
background-color: #000;
color: var(--dark-text-color);
}
.wy-side-nav-search .wy-dropdown > a,
.wy-side-nav-search > a {
color: var(--dark-text-color);
}
.wy-side-nav-search input[type="text"] {
border-left: 0px;
border-right: 0px;
border-top: 0px;
border-radius: 0px;
box-shadow: none;
border-bottom-color: #0c0;
background-color: #333;
color: var(--dark-text-color);
}
.theme-switcher {
background-color: #0b0c0d;
color: var(--dark-text-color);
}
writer-html4 .rst-content dl:not(.docutils) > dt,
writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) > dt {
background-color: #0b0b0b;
color: #007dce;
border-color: #282828;
}
.rst-content code,
.rst-content tt {
color: var(--dark-text-color);
}
writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list) > dt,
writer-html5
.rst-content
dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)
dl:not(.field-list)
> dt {
background-color: #0f0f0f;
color: #959595;
border-color: #2b2b2b;
}
.rst-content code,
.rst-content tt,
code {
background-color: #2d2d2d;
border-color: #1c1c1c;
}
.rst-content code.xref,
.rst-content tt.xref,
a .rst-content code,
a .rst-content tt {
color: #cecece;
}
.rst-content .hint,
.rst-content .important,
.rst-content .tip,
.rst-content .wy-alert-success.admonition,
.rst-content .wy-alert-success.admonition-todo,
.rst-content .wy-alert-success.attention,
.rst-content .wy-alert-success.caution,
.rst-content .wy-alert-success.danger,
.rst-content .wy-alert-success.error,
.rst-content .wy-alert-success.note,
.rst-content .wy-alert-success.seealso,
.rst-content .wy-alert-success.warning,
.wy-alert.wy-alert-success {
background-color: #00392e;
}
.rst-content .hint .admonition-title,
.rst-content .hint .wy-alert-title,
.rst-content .important .admonition-title,
.rst-content .important .wy-alert-title,
.rst-content .tip .admonition-title,
.rst-content .tip .wy-alert-title,
.rst-content .wy-alert-success.admonition-todo .admonition-title,
.rst-content .wy-alert-success.admonition-todo .wy-alert-title,
.rst-content .wy-alert-success.admonition .admonition-title,
.rst-content .wy-alert-success.admonition .wy-alert-title,
.rst-content .wy-alert-success.attention .admonition-title,
.rst-content .wy-alert-success.attention .wy-alert-title,
.rst-content .wy-alert-success.caution .admonition-title,
.rst-content .wy-alert-success.caution .wy-alert-title,
.rst-content .wy-alert-success.danger .admonition-title,
.rst-content .wy-alert-success.danger .wy-alert-title,
.rst-content .wy-alert-success.error .admonition-title,
.rst-content .wy-alert-success.error .wy-alert-title,
.rst-content .wy-alert-success.note .admonition-title,
.rst-content .wy-alert-success.note .wy-alert-title,
.rst-content .wy-alert-success.seealso .admonition-title,
.rst-content .wy-alert-success.seealso .wy-alert-title,
.rst-content .wy-alert-success.warning .admonition-title,
.rst-content .wy-alert-success.warning .wy-alert-title,
.rst-content .wy-alert.wy-alert-success .admonition-title,
.wy-alert.wy-alert-success .rst-content .admonition-title,
.wy-alert.wy-alert-success .wy-alert-title {
background-color: #006a56;
}
.rst-content .note,
.rst-content .seealso,
.rst-content .wy-alert-info.admonition,
.rst-content .wy-alert-info.admonition-todo,
.rst-content .wy-alert-info.attention,
.rst-content .wy-alert-info.caution,
.rst-content .wy-alert-info.danger,
.rst-content .wy-alert-info.error,
.rst-content .wy-alert-info.hint,
.rst-content .wy-alert-info.important,
.rst-content .wy-alert-info.tip,
.rst-content .wy-alert-info.warning,
.wy-alert.wy-alert-info {
background-color: #002c4d;
}
.rst-content .note .admonition-title,
.rst-content .note .wy-alert-title,
.rst-content .seealso .admonition-title,
.rst-content .seealso .wy-alert-title,
.rst-content .wy-alert-info.admonition-todo .admonition-title,
.rst-content .wy-alert-info.admonition-todo .wy-alert-title,
.rst-content .wy-alert-info.admonition .admonition-title,
.rst-content .wy-alert-info.admonition .wy-alert-title,
.rst-content .wy-alert-info.attention .admonition-title,
.rst-content .wy-alert-info.attention .wy-alert-title,
.rst-content .wy-alert-info.caution .admonition-title,
.rst-content .wy-alert-info.caution .wy-alert-title,
.rst-content .wy-alert-info.danger .admonition-title,
.rst-content .wy-alert-info.danger .wy-alert-title,
.rst-content .wy-alert-info.error .admonition-title,
.rst-content .wy-alert-info.error .wy-alert-title,
.rst-content .wy-alert-info.hint .admonition-title,
.rst-content .wy-alert-info.hint .wy-alert-title,
.rst-content .wy-alert-info.important .admonition-title,
.rst-content .wy-alert-info.important .wy-alert-title,
.rst-content .wy-alert-info.tip .admonition-title,
.rst-content .wy-alert-info.tip .wy-alert-title,
.rst-content .wy-alert-info.warning .admonition-title,
.rst-content .wy-alert-info.warning .wy-alert-title,
.rst-content .wy-alert.wy-alert-info .admonition-title,
.wy-alert.wy-alert-info .rst-content .admonition-title,
.wy-alert.wy-alert-info .wy-alert-title {
background-color: #004a7b;
}
.rst-content dl:not(.docutils) dt {
background-color: #333;
}
.rst-content {
color: var(--dark-text-color);
}
.rst-content .admonition-todo,
.rst-content .attention,
.rst-content .caution,
.rst-content .warning,
.rst-content .wy-alert-warning.admonition,
.rst-content .wy-alert-warning.danger,
.rst-content .wy-alert-warning.error,
.rst-content .wy-alert-warning.hint,
.rst-content .wy-alert-warning.important,
.rst-content .wy-alert-warning.note,
.rst-content .wy-alert-warning.seealso,
.rst-content .wy-alert-warning.tip,
.wy-alert.wy-alert-warning {
background-color: #533500;
}
.rst-content .admonition-todo .admonition-title,
.rst-content .admonition-todo .wy-alert-title,
.rst-content .attention .admonition-title,
.rst-content .attention .wy-alert-title,
.rst-content .caution .admonition-title,
.rst-content .caution .wy-alert-title,
.rst-content .warning .admonition-title,
.rst-content .warning .wy-alert-title,
.rst-content .wy-alert-warning.admonition .admonition-title,
.rst-content .wy-alert-warning.admonition .wy-alert-title,
.rst-content .wy-alert-warning.danger .admonition-title,
.rst-content .wy-alert-warning.danger .wy-alert-title,
.rst-content .wy-alert-warning.error .admonition-title,
.rst-content .wy-alert-warning.error .wy-alert-title,
.rst-content .wy-alert-warning.hint .admonition-title,
.rst-content .wy-alert-warning.hint .wy-alert-title,
.rst-content .wy-alert-warning.important .admonition-title,
.rst-content .wy-alert-warning.important .wy-alert-title,
.rst-content .wy-alert-warning.note .admonition-title,
.rst-content .wy-alert-warning.note .wy-alert-title,
.rst-content .wy-alert-warning.seealso .admonition-title,
.rst-content .wy-alert-warning.seealso .wy-alert-title,
.rst-content .wy-alert-warning.tip .admonition-title,
.rst-content .wy-alert-warning.tip .wy-alert-title,
.rst-content .wy-alert.wy-alert-warning .admonition-title,
.wy-alert.wy-alert-warning .rst-content .admonition-title,
.wy-alert.wy-alert-warning .wy-alert-title {
background-color: #803b00;
}
.rst-content .danger,
.rst-content .error,
.rst-content .wy-alert-danger.admonition,
.rst-content .wy-alert-danger.admonition-todo,
.rst-content .wy-alert-danger.attention,
.rst-content .wy-alert-danger.caution,
.rst-content .wy-alert-danger.hint,
.rst-content .wy-alert-danger.important,
.rst-content .wy-alert-danger.note,
.rst-content .wy-alert-danger.seealso,
.rst-content .wy-alert-danger.tip,
.rst-content .wy-alert-danger.warning,
.wy-alert.wy-alert-danger {
background-color: #82231a;
}
.rst-content .danger .admonition-title,
.rst-content .danger .wy-alert-title,
.rst-content .error .admonition-title,
.rst-content .error .wy-alert-title,
.rst-content .wy-alert-danger.admonition-todo .admonition-title,
.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,
.rst-content .wy-alert-danger.admonition .admonition-title,
.rst-content .wy-alert-danger.admonition .wy-alert-title,
.rst-content .wy-alert-danger.attention .admonition-title,
.rst-content .wy-alert-danger.attention .wy-alert-title,
.rst-content .wy-alert-danger.caution .admonition-title,
.rst-content .wy-alert-danger.caution .wy-alert-title,
.rst-content .wy-alert-danger.hint .admonition-title,
.rst-content .wy-alert-danger.hint .wy-alert-title,
.rst-content .wy-alert-danger.important .admonition-title,
.rst-content .wy-alert-danger.important .wy-alert-title,
.rst-content .wy-alert-danger.note .admonition-title,
.rst-content .wy-alert-danger.note .wy-alert-title,
.rst-content .wy-alert-danger.seealso .admonition-title,
.rst-content .wy-alert-danger.seealso .wy-alert-title,
.rst-content .wy-alert-danger.tip .admonition-title,
.rst-content .wy-alert-danger.tip .wy-alert-title,
.rst-content .wy-alert-danger.warning .admonition-title,
.rst-content .wy-alert-danger.warning .wy-alert-title,
.rst-content .wy-alert.wy-alert-danger .admonition-title,
.wy-alert.wy-alert-danger .rst-content .admonition-title,
.wy-alert.wy-alert-danger .wy-alert-title {
background-color: #b9372b;
}
.wy-nav-top {
background-color: #0b152d;
}
.rst-content table.docutils thead,
.rst-content table.field-list thead,
.wy-table thead {
color: var(--dark-text-color);
}
.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,
.wy-table-backed,
.wy-table-odd td,
.wy-table-striped tr:nth-child(2n-1) td {
background-color: #333;
}
.rst-content table.docutils:not(.field-list) tr:nth-child(2n) td,
.wy-table-backed,
.wy-table-odd td,
.wy-table-striped tr:nth-child(2n) td {
background-color: #444;
}
.rst-content table.docutils td,
.wy-table-bordered-all td,
writer-html5 .rst-content table.docutils th,
.rst-content table.docutils,
.wy-table-bordered-all {
border-color: #262626;
}
.rst-content table.docutils caption,
.rst-content table.field-list caption,
.wy-table caption {
color: var(--dark-text-color);
}
.wy-menu-vertical li.toctree-l3.current > a,
.wy-menu-vertical li.toctree-l3.current li.toctree-l4 > a {
background-color: #000;
}
.wy-side-nav-search > div.version {
color: var(--dark-text-color);
}

@ -1,12 +1,12 @@
{ {
"name": "bitburner", "name": "bitburner",
"version": "1.6.3", "version": "1.6.4",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bitburner", "name": "bitburner",
"version": "1.6.3", "version": "1.6.4",
"dependencies": { "dependencies": {
"electron-config": "^2.0.0", "electron-config": "^2.0.0",
"electron-log": "^4.4.4", "electron-log": "^4.4.4",

@ -1,6 +1,6 @@
{ {
"name": "bitburner", "name": "bitburner",
"version": "1.6.3", "version": "1.6.4",
"description": "A cyberpunk-themed programming incremental game", "description": "A cyberpunk-themed programming incremental game",
"main": "main.js", "main": "main.js",
"author": "Daniel Xie & Olivier Gagnon", "author": "Daniel Xie & Olivier Gagnon",

@ -0,0 +1,25 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Grafting](./bitburner.grafting.md) &gt; [getGraftableAugmentations](./bitburner.grafting.getgraftableaugmentations.md)
## Grafting.getGraftableAugmentations() method
Retrieves a list of Augmentations that can be grafted.
<b>Signature:</b>
```typescript
getGraftableAugmentations(): string[];
```
<b>Returns:</b>
string\[\]
An array of graftable Augmentations.
## Remarks
RAM cost: 5 GB
Note that this function returns a list of currently graftable Augmentations, based off of the Augmentations that you already own.

@ -22,5 +22,6 @@ This API requires Source-File 10 to use.
| --- | --- | | --- | --- |
| [getAugmentationGraftPrice(augName)](./bitburner.grafting.getaugmentationgraftprice.md) | Retrieve the grafting cost of an aug. | | [getAugmentationGraftPrice(augName)](./bitburner.grafting.getaugmentationgraftprice.md) | Retrieve the grafting cost of an aug. |
| [getAugmentationGraftTime(augName)](./bitburner.grafting.getaugmentationgrafttime.md) | Retrieves the time required to graft an aug. | | [getAugmentationGraftTime(augName)](./bitburner.grafting.getaugmentationgrafttime.md) | Retrieves the time required to graft an aug. |
| [getGraftableAugmentations()](./bitburner.grafting.getgraftableaugmentations.md) | Retrieves a list of Augmentations that can be grafted. |
| [graftAugmentation(augName, focus)](./bitburner.grafting.graftaugmentation.md) | Begins grafting the named aug. You must be in New Tokyo to use this. | | [graftAugmentation(augName, focus)](./bitburner.grafting.graftaugmentation.md) | Begins grafting the named aug. You must be in New Tokyo to use this. |

@ -9,7 +9,7 @@ Calculate the security increase for a number of thread.
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
growthAnalyzeSecurity(threads: number): number; growthAnalyzeSecurity(threads: number, hostname?: string, cores?: number): number;
``` ```
## Parameters ## Parameters
@ -17,6 +17,8 @@ growthAnalyzeSecurity(threads: number): number;
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| threads | number | Amount of threads that will be used. | | threads | number | Amount of threads that will be used. |
| hostname | string | Optional. Hostname of the target server. The number of threads is limited to the number needed to hack the servers maximum amount of money. |
| cores | number | Optional. The number of cores of the server that would run grow. |
<b>Returns:</b> <b>Returns:</b>

@ -115,7 +115,7 @@ export async function main(ns) {
| [getWeakenTime(host)](./bitburner.ns.getweakentime.md) | Get the execution time of a weaken() call. | | [getWeakenTime(host)](./bitburner.ns.getweakentime.md) | Get the execution time of a weaken() call. |
| [grow(host, opts)](./bitburner.ns.grow.md) | Spoof money in a servers bank account, increasing the amount available. | | [grow(host, opts)](./bitburner.ns.grow.md) | Spoof money in a servers bank account, increasing the amount available. |
| [growthAnalyze(host, growthAmount, cores)](./bitburner.ns.growthanalyze.md) | Calculate the number of grow thread needed to grow a server by a certain multiplier. | | [growthAnalyze(host, growthAmount, cores)](./bitburner.ns.growthanalyze.md) | Calculate the number of grow thread needed to grow a server by a certain multiplier. |
| [growthAnalyzeSecurity(threads)](./bitburner.ns.growthanalyzesecurity.md) | Calculate the security increase for a number of thread. | | [growthAnalyzeSecurity(threads, hostname, cores)](./bitburner.ns.growthanalyzesecurity.md) | Calculate the security increase for a number of thread. |
| [hack(host, opts)](./bitburner.ns.hack.md) | Steal a servers money. | | [hack(host, opts)](./bitburner.ns.hack.md) | Steal a servers money. |
| [hackAnalyze(host)](./bitburner.ns.hackanalyze.md) | Get the part of money stolen with a single thread. | | [hackAnalyze(host)](./bitburner.ns.hackanalyze.md) | Get the part of money stolen with a single thread. |
| [hackAnalyzeChance(host)](./bitburner.ns.hackanalyzechance.md) | Get the chance of successfully hacking a server. | | [hackAnalyzeChance(host)](./bitburner.ns.hackanalyzechance.md) | Get the chance of successfully hacking a server. |

4
package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "bitburner", "name": "bitburner",
"version": "1.6.3", "version": "1.6.4",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bitburner", "name": "bitburner",
"version": "1.6.3", "version": "1.6.4",
"hasInstallScript": true, "hasInstallScript": true,
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"dependencies": { "dependencies": {

@ -1,7 +1,7 @@
{ {
"name": "bitburner", "name": "bitburner",
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"version": "1.6.3", "version": "1.6.4",
"main": "electron-main.js", "main": "electron-main.js",
"author": { "author": {
"name": "Daniel Xie & Olivier Gagnon" "name": "Daniel Xie & Olivier Gagnon"

@ -431,9 +431,6 @@ export class Augmentation {
// Name of Augmentation // Name of Augmentation
name = ""; name = "";
// Whether the player owns this Augmentation
owned = false;
// Array of names of all prerequisites // Array of names of all prerequisites
prereqs: string[] = []; prereqs: string[] = [];

@ -7,7 +7,6 @@ import { CONSTANTS } from "../Constants";
import { Factions, factionExists } from "../Faction/Factions"; import { Factions, factionExists } from "../Faction/Factions";
import { Player } from "../Player"; import { Player } from "../Player";
import { prestigeAugmentation } from "../Prestige"; import { prestigeAugmentation } from "../Prestige";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { clearObject } from "../utils/helpers/clearObject"; import { clearObject } from "../utils/helpers/clearObject";
@ -75,7 +74,7 @@ function initAugmentations(): void {
} }
function getBaseAugmentationPriceMultiplier(): number { function getBaseAugmentationPriceMultiplier(): number {
return CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]]; return CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.sourceFileLvl(11)];
} }
export function getGenericAugmentationPriceMultiplier(): number { export function getGenericAugmentationPriceMultiplier(): number {
return Math.pow(getBaseAugmentationPriceMultiplier(), Player.queuedAugmentations.length); return Math.pow(getBaseAugmentationPriceMultiplier(), Player.queuedAugmentations.length);
@ -134,8 +133,6 @@ function resetAugmentation(aug: Augmentation): void {
} }
function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void { function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void {
Augmentations[aug.name].owned = true;
const augObj = Augmentations[aug.name]; const augObj = Augmentations[aug.name];
// Apply multipliers // Apply multipliers

@ -7,7 +7,6 @@ import * as React from "react";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentations } from "../Augmentations"; import { Augmentations } from "../Augmentations";
@ -35,7 +34,7 @@ interface IBitNodeModifiedStatsProps {
function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactElement { function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactElement {
// If player doesn't have SF5 or if the property isn't affected by BitNode mults // If player doesn't have SF5 or if the property isn't affected by BitNode mults
if (props.mult === 1 || SourceFileFlags[5] === 0) if (props.mult === 1 || Player.sourceFileLvl(5) === 0)
return <Typography color={props.color}>{numeralWrapper.formatPercentage(props.base)}</Typography>; return <Typography color={props.color}>{numeralWrapper.formatPercentage(props.base)}</Typography>;
return ( return (

@ -1,5 +1,4 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { BitNodes } from "../BitNode"; import { BitNodes } from "../BitNode";
import { enterBitNode } from "../../RedPill"; import { enterBitNode } from "../../RedPill";
@ -128,12 +127,6 @@ export function BitverseRoot(props: IProps): React.ReactElement {
const destroyed = player.bitNodeN; const destroyed = player.bitNodeN;
const [destroySequence, setDestroySequence] = useState(!props.quick); const [destroySequence, setDestroySequence] = useState(!props.quick);
// Update NextSourceFileFlags
const nextSourceFileFlags = SourceFileFlags.slice();
if (!props.flume) {
if (nextSourceFileFlags[destroyed] < 3) ++nextSourceFileFlags[destroyed];
}
if (destroySequence) { if (destroySequence) {
return ( return (
<CinematicText <CinematicText
@ -164,6 +157,15 @@ export function BitverseRoot(props: IProps): React.ReactElement {
); );
} }
const nextSourceFileLvl = (n: number): number => {
const lvl = player.sourceFileLvl(n);
if (n !== destroyed) {
return lvl;
}
const max = n === 12 ? Infinity : 3;
return Math.min(max, lvl + 1);
};
if (Settings.DisableASCIIArt) { if (Settings.DisableASCIIArt) {
return ( return (
<> <>
@ -176,7 +178,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<BitNodePortal <BitNodePortal
key={node.number} key={node.number}
n={node.number} n={node.number}
level={nextSourceFileFlags[node.number]} level={nextSourceFileLvl(node.number)}
enter={enter} enter={enter}
flume={props.flume} flume={props.flume}
destroyedBitNode={destroyed} destroyedBitNode={destroyed}
@ -215,6 +217,8 @@ export function BitverseRoot(props: IProps): React.ReactElement {
</> </>
); );
} }
const n = nextSourceFileLvl;
return ( return (
// prettier-ignore // prettier-ignore
<> <>
@ -228,19 +232,19 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>O | | | \| | O / _/ | / O | |/ | | | O</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>O | | | \| | O / _/ | / O | |/ | | | O</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | O / | O O | | \ O| | | |</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | O / | O O | | \ O| | | |</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| O | |_/ |\| \ <BitNodePortal n={13} level={nextSourceFileFlags[13]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \__| \_| | O |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| O | |_/ |\| \ <BitNodePortal n={13} level={n(13)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \__| \_| | O |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | |_/ | | \| / | \_| | | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | |_/ | | \| / | \_| | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| / \| | / / \ |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| / \| | / / \ |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | <BitNodePortal n={10} level={n(10)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={n(11)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={9} level={nextSourceFileFlags[9]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={nextSourceFileFlags[12]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={9} level={n(9)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={n(12)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | / / \ \ | | | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | / / \ \ | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| | / <BitNodePortal n={7} level={nextSourceFileFlags[7]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={nextSourceFileFlags[8]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| | / <BitNodePortal n={7} level={n(7)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={n(8)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ | / / | | \ \ | / </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ | / / | | \ \ | / </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \JUMP <BitNodePortal n={5} level={nextSourceFileFlags[5]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={nextSourceFileFlags[6]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \JUMP <BitNodePortal n={5} level={n(5)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={n(6)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \|| | | | | | | | | ||/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \|| | | | | | | | | ||/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| \_ | | | | | | _/ |/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| \_ | | | | | | _/ |/ </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \| / \ / \ |/ / </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \| / \ / \ |/ / </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={1} level={nextSourceFileFlags[1]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={nextSourceFileFlags[2]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={nextSourceFileFlags[3]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={nextSourceFileFlags[4]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={1} level={n(1)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={n(2)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={n(3)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={n(4)} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | | </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </Typography>
<br /> <br />

@ -28,7 +28,6 @@ import { Factions, factionExists } from "../Faction/Factions";
import { calculateHospitalizationCost } from "../Hospital/Hospital"; import { calculateHospitalizationCost } from "../Hospital/Hospital";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
import { Augmentations } from "../Augmentation/Augmentations";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { getTimestamp } from "../utils/helpers/getTimestamp"; import { getTimestamp } from "../utils/helpers/getTimestamp";
import { joinFaction } from "../Faction/FactionHelpers"; import { joinFaction } from "../Faction/FactionHelpers";
@ -1920,7 +1919,7 @@ export class Bladeburner implements IBladeburner {
} }
// If the Player starts doing some other actions, set action to idle and alert // If the Player starts doing some other actions, set action to idle and alert
if (Augmentations[AugmentationNames.BladesSimulacrum].owned === false && player.isWorking) { if (player.hasAugmentation(AugmentationNames.BladesSimulacrum) === false && player.isWorking) {
if (this.action.type !== ActionTypes["Idle"]) { if (this.action.type !== ActionTypes["Idle"]) {
let msg = "Your Bladeburner action was cancelled because you started doing something else."; let msg = "Your Bladeburner action was cancelled because you started doing something else.";
if (this.automateEnabled) { if (this.automateEnabled) {

@ -116,8 +116,8 @@ export const CONSTANTS: {
TotalNumBitNodes: number; TotalNumBitNodes: number;
LatestUpdate: string; LatestUpdate: string;
} = { } = {
VersionString: "1.6.3", VersionString: "1.6.4",
VersionNumber: 13, VersionNumber: 14,
// Speed (in ms) at which the main loop is updated // Speed (in ms) at which the main loop is updated
_idleSpeed: 200, _idleSpeed: 200,

@ -51,29 +51,31 @@ export function NewIndustry(corporation: ICorporation, industry: string, name: s
export function NewCity(corporation: ICorporation, division: IIndustry, city: string): void { export function NewCity(corporation: ICorporation, division: IIndustry, city: string): void {
if (corporation.funds < CorporationConstants.OfficeInitialCost) { if (corporation.funds < CorporationConstants.OfficeInitialCost) {
throw new Error("You don't have enough company funds to open a new office!"); throw new Error("You don't have enough company funds to open a new office!");
} else {
corporation.funds = corporation.funds - CorporationConstants.OfficeInitialCost;
division.offices[city] = new OfficeSpace({
loc: city,
size: CorporationConstants.OfficeInitialSize,
});
} }
if (division.offices[city]) {
throw new Error(`You have already expanded into ${city} for ${division.name}`);
}
corporation.funds = corporation.funds - CorporationConstants.OfficeInitialCost;
division.offices[city] = new OfficeSpace({
loc: city,
size: CorporationConstants.OfficeInitialSize,
});
} }
export function UnlockUpgrade(corporation: ICorporation, upgrade: CorporationUnlockUpgrade): void { export function UnlockUpgrade(corporation: ICorporation, upgrade: CorporationUnlockUpgrade): void {
if (corporation.funds < upgrade[1]) { if (corporation.funds < upgrade.price) {
throw new Error("Insufficient funds"); throw new Error("Insufficient funds");
} }
if (corporation.unlockUpgrades[upgrade[0]] === 1) { if (corporation.unlockUpgrades[upgrade.index] === 1) {
throw new Error(`You have already unlocked the ${upgrade[2]} upgrade!`); throw new Error(`You have already unlocked the ${upgrade.name} upgrade!`);
} }
corporation.unlock(upgrade); corporation.unlock(upgrade);
} }
export function LevelUpgrade(corporation: ICorporation, upgrade: CorporationUpgrade): void { export function LevelUpgrade(corporation: ICorporation, upgrade: CorporationUpgrade): void {
const baseCost = upgrade[1]; const baseCost = upgrade.basePrice;
const priceMult = upgrade[2]; const priceMult = upgrade.priceMult;
const level = corporation.upgrades[upgrade[0]]; const level = corporation.upgrades[upgrade.index];
const cost = baseCost * Math.pow(priceMult, level); const cost = baseCost * Math.pow(priceMult, level);
if (corporation.funds < cost) { if (corporation.funds < cost) {
throw new Error("Insufficient funds"); throw new Error("Insufficient funds");
@ -345,10 +347,17 @@ export function PurchaseWarehouse(corp: ICorporation, division: IIndustry, city:
corp.funds = corp.funds - CorporationConstants.WarehouseInitialCost; corp.funds = corp.funds - CorporationConstants.WarehouseInitialCost;
} }
export function UpgradeWarehouse(corp: ICorporation, division: IIndustry, warehouse: Warehouse): void { export function UpgradeWarehouseCost(warehouse: Warehouse, amt: number): number {
const sizeUpgradeCost = CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1); return Array.from(Array(amt).keys()).reduce(
(acc, index) => acc + CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1 + index),
0,
);
}
export function UpgradeWarehouse(corp: ICorporation, division: IIndustry, warehouse: Warehouse, amt = 1): void {
const sizeUpgradeCost = UpgradeWarehouseCost(warehouse, amt);
if (corp.funds < sizeUpgradeCost) return; if (corp.funds < sizeUpgradeCost) return;
++warehouse.level; warehouse.level += amt;
warehouse.updateSize(corp, division); warehouse.updateSize(corp, division);
corp.funds = corp.funds - sizeUpgradeCost; corp.funds = corp.funds - sizeUpgradeCost;
} }
@ -416,7 +425,7 @@ export function MakeProduct(
} }
const product = new Product({ const product = new Product({
name: productName.replace(/[<>]/g, ""), //Sanitize for HTMl elements name: productName.replace(/[<>]/g, "").trim(), //Sanitize for HTMl elements
createCity: city, createCity: city,
designCost: designInvest, designCost: designInvest,
advCost: marketingInvest, advCost: marketingInvest,
@ -488,12 +497,23 @@ export function CancelExportMaterial(divisionName: string, cityName: string, mat
export function LimitProductProduction(product: Product, cityName: string, qty: number): void { export function LimitProductProduction(product: Product, cityName: string, qty: number): void {
if (qty < 0 || isNaN(qty)) { if (qty < 0 || isNaN(qty)) {
product.prdman[cityName][0] = false; product.prdman[cityName][0] = false;
product.prdman[cityName][1] = 0;
} else { } else {
product.prdman[cityName][0] = true; product.prdman[cityName][0] = true;
product.prdman[cityName][1] = qty; product.prdman[cityName][1] = qty;
} }
} }
export function LimitMaterialProduction(material: Material, qty: number): void {
if (qty < 0 || isNaN(qty)) {
material.prdman[0] = false;
material.prdman[1] = 0;
} else {
material.prdman[0] = true;
material.prdman[1] = qty;
}
}
export function SetMaterialMarketTA1(material: Material, on: boolean): void { export function SetMaterialMarketTA1(material: Material, on: boolean): void {
material.marketTa1 = on; material.marketTa1 = on;
} }

@ -45,6 +45,8 @@ export class Corporation {
upgrades: number[]; upgrades: number[];
upgradeMultipliers: number[]; upgradeMultipliers: number[];
avgProfit = 0;
state = new CorporationState(); state = new CorporationState();
constructor(params: IParams = {}) { constructor(params: IParams = {}) {
@ -106,6 +108,8 @@ export class Corporation {
this.expenses = this.expenses + ind.lastCycleExpenses; this.expenses = this.expenses + ind.lastCycleExpenses;
}); });
const profit = this.revenue - this.expenses; const profit = this.revenue - this.expenses;
this.avgProfit =
(this.avgProfit * (CorporationConstants.AvgProfitLength - 1) + profit) / CorporationConstants.AvgProfitLength;
const cycleProfit = profit * (marketCycles * CorporationConstants.SecsPerMarketCycle); const cycleProfit = profit * (marketCycles * CorporationConstants.SecsPerMarketCycle);
if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) { if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
dialogBoxCreate( dialogBoxCreate(
@ -160,7 +164,7 @@ export class Corporation {
determineValuation(): number { determineValuation(): number {
let val, let val,
profit = this.revenue - this.expenses; profit = this.avgProfit;
if (this.public) { if (this.public) {
// Account for dividends // Account for dividends
if (this.dividendPercentage > 0) { if (this.dividendPercentage > 0) {
@ -260,8 +264,8 @@ export class Corporation {
//One time upgrades that unlock new features //One time upgrades that unlock new features
unlock(upgrade: CorporationUnlockUpgrade): void { unlock(upgrade: CorporationUnlockUpgrade): void {
const upgN = upgrade[0], const upgN = upgrade.index,
price = upgrade[1]; price = upgrade.price;
while (this.unlockUpgrades.length <= upgN) { while (this.unlockUpgrades.length <= upgN) {
this.unlockUpgrades.push(0); this.unlockUpgrades.push(0);
} }
@ -282,10 +286,10 @@ export class Corporation {
//Levelable upgrades //Levelable upgrades
upgrade(upgrade: CorporationUpgrade): void { upgrade(upgrade: CorporationUpgrade): void {
const upgN = upgrade[0], const upgN = upgrade.index,
basePrice = upgrade[1], basePrice = upgrade.basePrice,
priceMult = upgrade[2], priceMult = upgrade.priceMult,
upgradeAmt = upgrade[3]; //Amount by which the upgrade multiplier gets increased (additive) upgradeAmt = upgrade.benefit; //Amount by which the upgrade multiplier gets increased (additive)
while (this.upgrades.length <= upgN) { while (this.upgrades.length <= upgN) {
this.upgrades.push(0); this.upgrades.push(0);
} }

@ -28,6 +28,7 @@ export const CorporationConstants: {
AllMaterials: string[]; AllMaterials: string[];
FundingRoundShares: number[]; FundingRoundShares: number[];
FundingRoundMultiplier: number[]; FundingRoundMultiplier: number[];
AvgProfitLength: number;
} = { } = {
INITIALSHARES: 1e9, //Total number of shares you have at your company INITIALSHARES: 1e9, //Total number of shares you have at your company
SHARESPERPRICEUPDATE: 1e6, //When selling large number of shares, price is dynamically updated for every batch of this amount SHARESPERPRICEUPDATE: 1e6, //When selling large number of shares, price is dynamically updated for every batch of this amount
@ -83,4 +84,6 @@ export const CorporationConstants: {
], ],
FundingRoundShares: [0.1, 0.35, 0.25, 0.2], FundingRoundShares: [0.1, 0.35, 0.25, 0.2],
FundingRoundMultiplier: [4, 3, 3, 2.5], FundingRoundMultiplier: [4, 3, 3, 2.5],
AvgProfitLength: 1,
}; };

@ -1,70 +1,100 @@
import { IMap } from "../../types"; export interface CorporationUnlockUpgrade {
index: number;
price: number;
name: string;
desc: string;
}
export type CorporationUnlockUpgrade = [number, number, string, string]; export enum CorporationUnlockUpgradeIndex {
Export = 0,
SmartSupply = 1,
MarketResearchDemand = 2,
MarketDataCompetition = 3,
VeChain = 4,
ShadyAccounting = 5,
GovernmentPartnership = 6,
WarehouseAPI = 7,
OfficeAPI = 8,
}
// Corporation Unlock Upgrades // Corporation Unlock Upgrades
// Upgrades for entire corporation, unlocks features, either you have it or you dont // Upgrades for entire corporation, unlocks features, either you have it or you dont
// The data structure is an array with the following format: export const CorporationUnlockUpgrades: Record<CorporationUnlockUpgradeIndex, CorporationUnlockUpgrade> = {
// [index in Corporation feature upgrades array, price, name, description]
export const CorporationUnlockUpgrades: IMap<CorporationUnlockUpgrade> = {
//Lets you export goods //Lets you export goods
"0": [ [CorporationUnlockUpgradeIndex.Export]: {
0, index: 0,
20e9, price: 20e9,
"Export", name: "Export",
"Develop infrastructure to export your materials to your other facilities. " + desc:
"Develop infrastructure to export your materials to your other facilities. " +
"This allows you to move materials around between different divisions and cities.", "This allows you to move materials around between different divisions and cities.",
], },
//Lets you buy exactly however many required materials you need for production //Lets you buy exactly however many required materials you need for production
"1": [ [CorporationUnlockUpgradeIndex.SmartSupply]: {
1, index: 1,
25e9, price: 25e9,
"Smart Supply", name: "Smart Supply",
"Use advanced AI to anticipate your supply needs. " + desc:
"Use advanced AI to anticipate your supply needs. " +
"This allows you to purchase exactly however many materials you need for production.", "This allows you to purchase exactly however many materials you need for production.",
], },
//Displays each material/product's demand //Displays each material/product's demand
"2": [ [CorporationUnlockUpgradeIndex.MarketResearchDemand]: {
2, index: 2,
5e9, price: 5e9,
"Market Research - Demand", name: "Market Research - Demand",
"Mine and analyze market data to determine the demand of all resources. " + desc:
"Mine and analyze market data to determine the demand of all resources. " +
"The demand attribute, which affects sales, will be displayed for every material and product.", "The demand attribute, which affects sales, will be displayed for every material and product.",
], },
//Display's each material/product's competition //Display's each material/product's competition
"3": [ [CorporationUnlockUpgradeIndex.MarketDataCompetition]: {
3, index: 3,
5e9, price: 5e9,
"Market Data - Competition", name: "Market Data - Competition",
"Mine and analyze market data to determine how much competition there is on the market " + desc:
"Mine and analyze market data to determine how much competition there is on the market " +
"for all resources. The competition attribute, which affects sales, will be displayed for " + "for all resources. The competition attribute, which affects sales, will be displayed for " +
"every material and product.", "every material and product.",
], },
"4": [ [CorporationUnlockUpgradeIndex.VeChain]: {
4, index: 4,
10e9, price: 10e9,
"VeChain", name: "VeChain",
"Use AI and blockchain technology to identify where you can improve your supply chain systems. " + desc:
"Use AI and blockchain technology to identify where you can improve your supply chain systems. " +
"This upgrade will allow you to view a wide array of useful statistics about your " + "This upgrade will allow you to view a wide array of useful statistics about your " +
"Corporation.", "Corporation.",
], },
"5": [ [CorporationUnlockUpgradeIndex.ShadyAccounting]: {
5, index: 5,
500e12, price: 500e12,
"Shady Accounting", name: "Shady Accounting",
"Utilize unscrupulous accounting practices and pay off government officials to save money " + desc:
"Utilize unscrupulous accounting practices and pay off government officials to save money " +
"on taxes. This reduces the dividend tax rate by 5%.", "on taxes. This reduces the dividend tax rate by 5%.",
], },
"6": [ [CorporationUnlockUpgradeIndex.GovernmentPartnership]: {
6, index: 6,
2e15, price: 2e15,
"Government Partnership", name: "Government Partnership",
"Help national governments further their agendas in exchange for lowered taxes. " + desc:
"Help national governments further their agendas in exchange for lowered taxes. " +
"This reduces the dividend tax rate by 10%", "This reduces the dividend tax rate by 10%",
], },
"7": [7, 50e9, "Warehouse API", "Enables the warehouse API."], [CorporationUnlockUpgradeIndex.WarehouseAPI]: {
"8": [8, 50e9, "Office API", "Enables the office API."], index: 7,
price: 50e9,
name: "Warehouse API",
desc: "Enables the warehouse API.",
},
[CorporationUnlockUpgradeIndex.OfficeAPI]: {
index: 8,
price: 50e9,
name: "Office API",
desc: "Enables the office API.",
},
}; };

@ -1,127 +1,153 @@
import { IMap } from "../../types"; export interface CorporationUpgrade {
index: number;
basePrice: number;
priceMult: number;
benefit: number;
name: string;
desc: string;
}
export type CorporationUpgrade = [number, number, number, number, string, string]; export enum CorporationUpgradeIndex {
SmartFactories = 0,
SmartStorage = 1,
DreamSense = 2,
WilsonAnalytics = 3,
NuoptimalNootropicInjectorImplants = 4,
SpeechProcessorImplants = 5,
NeuralAccelerators = 6,
FocusWires = 7,
ABCSalesBots = 8,
ProjectInsight = 9,
}
// Corporation Upgrades // Corporation Upgrades
// Upgrades for entire corporation, levelable upgrades // Upgrades for entire corporation, levelable upgrades
// The data structure is an array with the following format export const CorporationUpgrades: Record<CorporationUpgradeIndex, CorporationUpgrade> = {
// [index in Corporation upgrades array, base price, price mult, benefit mult (additive), name, desc]
export const CorporationUpgrades: IMap<CorporationUpgrade> = {
//Smart factories, increases production //Smart factories, increases production
"0": [ [CorporationUpgradeIndex.SmartFactories]: {
0, index: CorporationUpgradeIndex.SmartFactories,
2e9, basePrice: 2e9,
1.06, priceMult: 1.06,
0.03, benefit: 0.03,
"Smart Factories", name: "Smart Factories",
"Advanced AI automatically optimizes the operation and productivity " + desc:
"Advanced AI automatically optimizes the operation and productivity " +
"of factories. Each level of this upgrade increases your global production by 3% (additive).", "of factories. Each level of this upgrade increases your global production by 3% (additive).",
], },
//Smart warehouses, increases storage size //Smart warehouses, increases storage size
"1": [ [CorporationUpgradeIndex.SmartStorage]: {
1, index: CorporationUpgradeIndex.SmartStorage,
2e9, basePrice: 2e9,
1.06, priceMult: 1.06,
0.1, benefit: 0.1,
"Smart Storage", name: "Smart Storage",
"Advanced AI automatically optimizes your warehouse storage methods. " + desc:
"Advanced AI automatically optimizes your warehouse storage methods. " +
"Each level of this upgrade increases your global warehouse storage size by 10% (additive).", "Each level of this upgrade increases your global warehouse storage size by 10% (additive).",
], },
//Advertise through dreams, passive popularity/ awareness gain //Advertise through dreams, passive popularity/ awareness gain
"2": [ [CorporationUpgradeIndex.DreamSense]: {
2, index: CorporationUpgradeIndex.DreamSense,
4e9, basePrice: 4e9,
1.1, priceMult: 1.1,
0.001, benefit: 0.001,
"DreamSense", name: "DreamSense",
"Use DreamSense LCC Technologies to advertise your corporation " + desc:
"Use DreamSense LCC Technologies to advertise your corporation " +
"to consumers through their dreams. Each level of this upgrade provides a passive " + "to consumers through their dreams. Each level of this upgrade provides a passive " +
"increase in awareness of all of your companies (divisions) by 0.004 / market cycle," + "increase in awareness of all of your companies (divisions) by 0.004 / market cycle," +
"and in popularity by 0.001 / market cycle. A market cycle is approximately " + "and in popularity by 0.001 / market cycle. A market cycle is approximately " +
"15 seconds.", "15 seconds.",
], },
//Makes advertising more effective //Makes advertising more effective
"3": [ [CorporationUpgradeIndex.WilsonAnalytics]: {
3, index: CorporationUpgradeIndex.WilsonAnalytics,
4e9, basePrice: 4e9,
1.5, priceMult: 1.5,
0.005, benefit: 0.005,
"Wilson Analytics", name: "Wilson Analytics",
"Purchase data and analysis from Wilson, a marketing research " + desc:
"Purchase data and analysis from Wilson, a marketing research " +
"firm. Each level of this upgrades increases the effectiveness of your " + "firm. Each level of this upgrades increases the effectiveness of your " +
"advertising by 0.5% (additive).", "advertising by 0.5% (additive).",
], },
//Augmentation for employees, increases cre //Augmentation for employees, increases cre
"4": [ [CorporationUpgradeIndex.NuoptimalNootropicInjectorImplants]: {
4, index: CorporationUpgradeIndex.NuoptimalNootropicInjectorImplants,
1e9, basePrice: 1e9,
1.06, priceMult: 1.06,
0.1, benefit: 0.1,
"Nuoptimal Nootropic Injector Implants", name: "Nuoptimal Nootropic Injector Implants",
"Purchase the Nuoptimal Nootropic " + desc:
"Purchase the Nuoptimal Nootropic " +
"Injector augmentation for your employees. Each level of this upgrade " + "Injector augmentation for your employees. Each level of this upgrade " +
"globally increases the creativity of your employees by 10% (additive).", "globally increases the creativity of your employees by 10% (additive).",
], },
//Augmentation for employees, increases cha //Augmentation for employees, increases cha
"5": [ [CorporationUpgradeIndex.SpeechProcessorImplants]: {
5, index: CorporationUpgradeIndex.SpeechProcessorImplants,
1e9, basePrice: 1e9,
1.06, priceMult: 1.06,
0.1, benefit: 0.1,
"Speech Processor Implants", name: "Speech Processor Implants",
"Purchase the Speech Processor augmentation for your employees. " + desc:
"Purchase the Speech Processor augmentation for your employees. " +
"Each level of this upgrade globally increases the charisma of your employees by 10% (additive).", "Each level of this upgrade globally increases the charisma of your employees by 10% (additive).",
], },
//Augmentation for employees, increases int //Augmentation for employees, increases int
"6": [ [CorporationUpgradeIndex.NeuralAccelerators]: {
6, index: CorporationUpgradeIndex.NeuralAccelerators,
1e9, basePrice: 1e9,
1.06, priceMult: 1.06,
0.1, benefit: 0.1,
"Neural Accelerators", name: "Neural Accelerators",
"Purchase the Neural Accelerator augmentation for your employees. " + desc:
"Purchase the Neural Accelerator augmentation for your employees. " +
"Each level of this upgrade globally increases the intelligence of your employees " + "Each level of this upgrade globally increases the intelligence of your employees " +
"by 10% (additive).", "by 10% (additive).",
], },
//Augmentation for employees, increases eff //Augmentation for employees, increases eff
"7": [ [CorporationUpgradeIndex.FocusWires]: {
7, index: CorporationUpgradeIndex.FocusWires,
1e9, basePrice: 1e9,
1.06, priceMult: 1.06,
0.1, benefit: 0.1,
"FocusWires", name: "FocusWires",
"Purchase the FocusWire augmentation for your employees. Each level " + desc:
"Purchase the FocusWire augmentation for your employees. Each level " +
"of this upgrade globally increases the efficiency of your employees by 10% (additive).", "of this upgrade globally increases the efficiency of your employees by 10% (additive).",
], },
//Improves sales of materials/products //Improves sales of materials/products
"8": [ [CorporationUpgradeIndex.ABCSalesBots]: {
8, index: CorporationUpgradeIndex.ABCSalesBots,
1e9, basePrice: 1e9,
1.07, priceMult: 1.07,
0.01, benefit: 0.01,
"ABC SalesBots", name: "ABC SalesBots",
"Always Be Closing. Purchase these robotic salesmen to increase the amount of " + desc:
"Always Be Closing. Purchase these robotic salesmen to increase the amount of " +
"materials and products you sell. Each level of this upgrade globally increases your sales " + "materials and products you sell. Each level of this upgrade globally increases your sales " +
"by 1% (additive).", "by 1% (additive).",
], },
//Improves scientific research rate //Improves scientific research rate
"9": [ [CorporationUpgradeIndex.ProjectInsight]: {
9, index: CorporationUpgradeIndex.ProjectInsight,
5e9, basePrice: 5e9,
1.07, priceMult: 1.07,
0.05, benefit: 0.05,
"Project Insight", name: "Project Insight",
"Purchase 'Project Insight', a R&D service provided by the secretive " + desc:
"Purchase 'Project Insight', a R&D service provided by the secretive " +
"Fulcrum Technologies. Each level of this upgrade globally increases the amount of " + "Fulcrum Technologies. Each level of this upgrade globally increases the amount of " +
"Scientific Research you produce by 5% (additive).", "Scientific Research you produce by 5% (additive).",
], },
}; };

@ -8,8 +8,8 @@ import { EmployeePositions } from "../EmployeePositions";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { UpgradeOfficeSizeModal } from "./UpgradeOfficeSizeModal"; import { UpgradeOfficeSizeModal } from "./modals/UpgradeOfficeSizeModal";
import { ThrowPartyModal } from "./ThrowPartyModal"; import { ThrowPartyModal } from "./modals/ThrowPartyModal";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { useCorporation, useDivision } from "./Context"; import { useCorporation, useDivision } from "./Context";

@ -7,8 +7,8 @@ import { Industries } from "../IndustryData";
import { IndustryUpgrades } from "../IndustryUpgrades"; import { IndustryUpgrades } from "../IndustryUpgrades";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { MakeProductModal } from "./MakeProductModal"; import { MakeProductModal } from "./modals/MakeProductModal";
import { ResearchModal } from "./ResearchModal"; import { ResearchModal } from "./modals/ResearchModal";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { MoneyRate } from "../../ui/React/MoneyRate"; import { MoneyRate } from "../../ui/React/MoneyRate";
import { StatsTable } from "../../ui/React/StatsTable"; import { StatsTable } from "../../ui/React/StatsTable";

@ -6,7 +6,7 @@ import { CorporationConstants } from "../data/Constants";
import { Material } from "../Material"; import { Material } from "../Material";
import { Product } from "../Product"; import { Product } from "../Product";
import { Warehouse } from "../Warehouse"; import { Warehouse } from "../Warehouse";
import { SmartSupplyModal } from "./SmartSupplyModal"; import { SmartSupplyModal } from "./modals/SmartSupplyModal";
import { ProductElem } from "./ProductElem"; import { ProductElem } from "./ProductElem";
import { MaterialElem } from "./MaterialElem"; import { MaterialElem } from "./MaterialElem";
import { MaterialSizes } from "../MaterialSizes"; import { MaterialSizes } from "../MaterialSizes";

@ -20,13 +20,13 @@ interface IProps {
export function LevelableUpgrade(props: IProps): React.ReactElement { export function LevelableUpgrade(props: IProps): React.ReactElement {
const corp = useCorporation(); const corp = useCorporation();
const data = props.upgrade; const data = props.upgrade;
const level = corp.upgrades[data[0]]; const level = corp.upgrades[data.index];
const baseCost = data[1]; const baseCost = data.basePrice;
const priceMult = data[2]; const priceMult = data.priceMult;
const cost = baseCost * Math.pow(priceMult, level); const cost = baseCost * Math.pow(priceMult, level);
const tooltip = data[5]; const tooltip = data.desc;
function onClick(): void { function onClick(): void {
if (corp.funds < cost) return; if (corp.funds < cost) return;
try { try {
@ -45,7 +45,7 @@ export function LevelableUpgrade(props: IProps): React.ReactElement {
</Button> </Button>
<Tooltip title={tooltip}> <Tooltip title={tooltip}>
<Typography> <Typography>
{data[4]} - lvl {level} {data.name} - lvl {level}
</Typography> </Typography>
</Tooltip> </Tooltip>
</Box> </Box>

@ -5,10 +5,10 @@ import React, { useState } from "react";
import { OfficeSpace } from "../OfficeSpace"; import { OfficeSpace } from "../OfficeSpace";
import { Material } from "../Material"; import { Material } from "../Material";
import { Warehouse } from "../Warehouse"; import { Warehouse } from "../Warehouse";
import { ExportModal } from "./ExportModal"; import { ExportModal } from "./modals/ExportModal";
import { MaterialMarketTaModal } from "./MaterialMarketTaModal"; import { MaterialMarketTaModal } from "./modals/MaterialMarketTaModal";
import { SellMaterialModal } from "./SellMaterialModal"; import { SellMaterialModal } from "./modals/SellMaterialModal";
import { PurchaseMaterialModal } from "./PurchaseMaterialModal"; import { PurchaseMaterialModal } from "./modals/PurchaseMaterialModal";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
@ -21,6 +21,7 @@ import Tooltip from "@mui/material/Tooltip";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import { LimitMaterialProductionModal } from "./modals/LimitMaterialProductionModal";
interface IMaterialProps { interface IMaterialProps {
warehouse: Warehouse; warehouse: Warehouse;
@ -37,6 +38,8 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
const [exportOpen, setExportOpen] = useState(false); const [exportOpen, setExportOpen] = useState(false);
const [sellMaterialOpen, setSellMaterialOpen] = useState(false); const [sellMaterialOpen, setSellMaterialOpen] = useState(false);
const [materialMarketTaOpen, setMaterialMarketTaOpen] = useState(false); const [materialMarketTaOpen, setMaterialMarketTaOpen] = useState(false);
const [limitProductionOpen, setLimitProductionOpen] = useState(false);
const warehouse = props.warehouse; const warehouse = props.warehouse;
const city = props.city; const city = props.city;
const mat = props.mat; const mat = props.mat;
@ -110,6 +113,12 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
sellButtonText = <>Sell (0.000/0.000)</>; sellButtonText = <>Sell (0.000/0.000)</>;
} }
// Limit Production button
let limitMaterialButtonText = "Limit Material";
if (mat.prdman[0]) {
limitMaterialButtonText += " (" + numeralWrapper.format(mat.prdman[1], nf) + ")";
}
return ( return (
<Paper> <Paper>
<Box sx={{ display: "grid", gridTemplateColumns: "2fr 1fr", m: "5px" }}> <Box sx={{ display: "grid", gridTemplateColumns: "2fr 1fr", m: "5px" }}>
@ -194,6 +203,14 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
/> />
</> </>
)} )}
<Button color={tutorial ? "error" : "primary"} onClick={() => setLimitProductionOpen(true)}>
{limitMaterialButtonText}
</Button>
<LimitMaterialProductionModal
material={mat}
open={limitProductionOpen}
onClose={() => setLimitProductionOpen(false)}
/>
</Box> </Box>
</Box> </Box>
</Paper> </Paper>

@ -2,18 +2,18 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { LevelableUpgrade } from "./LevelableUpgrade"; import { LevelableUpgrade } from "./LevelableUpgrade";
import { UnlockUpgrade } from "./UnlockUpgrade"; import { UnlockUpgrade } from "./UnlockUpgrade";
import { BribeFactionModal } from "./BribeFactionModal"; import { BribeFactionModal } from "./modals/BribeFactionModal";
import { SellSharesModal } from "./SellSharesModal"; import { SellSharesModal } from "./modals/SellSharesModal";
import { BuybackSharesModal } from "./BuybackSharesModal"; import { BuybackSharesModal } from "./modals/BuybackSharesModal";
import { IssueDividendsModal } from "./IssueDividendsModal"; import { IssueDividendsModal } from "./modals/IssueDividendsModal";
import { IssueNewSharesModal } from "./IssueNewSharesModal"; import { IssueNewSharesModal } from "./modals/IssueNewSharesModal";
import { FindInvestorsModal } from "./FindInvestorsModal"; import { FindInvestorsModal } from "./modals/FindInvestorsModal";
import { GoPublicModal } from "./GoPublicModal"; import { GoPublicModal } from "./modals/GoPublicModal";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";
import { CorporationUnlockUpgrade, CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades"; import { CorporationUnlockUpgrade, CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades";
import { CorporationUpgrade, CorporationUpgrades } from "../data/CorporationUpgrades"; import { CorporationUpgrade, CorporationUpgradeIndex, CorporationUpgrades } from "../data/CorporationUpgrades";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
@ -164,9 +164,9 @@ function Upgrades({ rerender }: IUpgradeProps): React.ReactElement {
<Typography variant="h4">Unlocks</Typography> <Typography variant="h4">Unlocks</Typography>
<Grid container> <Grid container>
{Object.values(CorporationUnlockUpgrades) {Object.values(CorporationUnlockUpgrades)
.filter((upgrade: CorporationUnlockUpgrade) => !corp.unlockUpgrades[upgrade[0]]) .filter((upgrade: CorporationUnlockUpgrade) => !corp.unlockUpgrades[upgrade.index])
.map((upgrade: CorporationUnlockUpgrade) => ( .map((upgrade: CorporationUnlockUpgrade) => (
<UnlockUpgrade rerender={rerender} upgradeData={upgrade} key={upgrade[0]} /> <UnlockUpgrade rerender={rerender} upgradeData={upgrade} key={upgrade.index} />
))} ))}
</Grid> </Grid>
</Paper> </Paper>
@ -174,9 +174,9 @@ function Upgrades({ rerender }: IUpgradeProps): React.ReactElement {
<Typography variant="h4">Upgrades</Typography> <Typography variant="h4">Upgrades</Typography>
<Grid container> <Grid container>
{corp.upgrades {corp.upgrades
.map((level: number, i: number) => CorporationUpgrades[i]) .map((level: number, i: number) => CorporationUpgrades[i as CorporationUpgradeIndex])
.map((upgrade: CorporationUpgrade) => ( .map((upgrade: CorporationUpgrade) => (
<LevelableUpgrade rerender={rerender} upgrade={upgrade} key={upgrade[0]} /> <LevelableUpgrade rerender={rerender} upgrade={upgrade} key={upgrade.index} />
))} ))}
</Grid> </Grid>
</Paper> </Paper>

@ -2,10 +2,11 @@ import React, { useState } from "react";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";
import { Product } from "../Product"; import { Product } from "../Product";
import { DiscontinueProductModal } from "./DiscontinueProductModal"; import { DiscontinueProductModal } from "./modals/DiscontinueProductModal";
import { LimitProductProductionModal } from "./LimitProductProductionModal"; import { LimitProductProductionModal } from "./modals/LimitProductProductionModal";
import { SellProductModal } from "./SellProductModal"; import { SellProductModal } from "./modals/SellProductModal";
import { ProductMarketTaModal } from "./ProductMarketTaModal"; import { ProductMarketTaModal } from "./modals/ProductMarketTaModal";
import { CancelProductModal } from "./modals/CancelProductModal";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
@ -32,6 +33,7 @@ export function ProductElem(props: IProductProps): React.ReactElement {
const [sellOpen, setSellOpen] = useState(false); const [sellOpen, setSellOpen] = useState(false);
const [limitOpen, setLimitOpen] = useState(false); const [limitOpen, setLimitOpen] = useState(false);
const [discontinueOpen, setDiscontinueOpen] = useState(false); const [discontinueOpen, setDiscontinueOpen] = useState(false);
const [cancelOpen, setCancelOpen] = useState(false);
const [marketTaOpen, setMarketTaOpen] = useState(false); const [marketTaOpen, setMarketTaOpen] = useState(false);
const city = props.city; const city = props.city;
const product = props.product; const product = props.product;
@ -111,6 +113,13 @@ export function ProductElem(props: IProductProps): React.ReactElement {
</Typography> </Typography>
<br /> <br />
<Typography>{numeralWrapper.format(product.prog, "0.00")}% complete</Typography> <Typography>{numeralWrapper.format(product.prog, "0.00")}% complete</Typography>
<Button onClick={() => setCancelOpen(true)}>Cancel</Button>
<CancelProductModal
product={product}
rerender={props.rerender}
open={cancelOpen}
onClose={() => setCancelOpen(false)}
/>
</> </>
) : ( ) : (
<> <>
@ -171,6 +180,13 @@ export function ProductElem(props: IProductProps): React.ReactElement {
<Typography>Est. Market Price: {numeralWrapper.formatMoney(product.pCost)}</Typography> <Typography>Est. Market Price: {numeralWrapper.formatMoney(product.pCost)}</Typography>
</Tooltip> </Tooltip>
</Box> </Box>
<Button onClick={() => setDiscontinueOpen(true)}>Discontinue</Button>
<DiscontinueProductModal
product={product}
rerender={props.rerender}
open={discontinueOpen}
onClose={() => setDiscontinueOpen(false)}
/>
</> </>
)} )}
@ -186,14 +202,6 @@ export function ProductElem(props: IProductProps): React.ReactElement {
open={limitOpen} open={limitOpen}
onClose={() => setLimitOpen(false)} onClose={() => setLimitOpen(false)}
/> />
<Button onClick={() => setDiscontinueOpen(true)}>Discontinue</Button>
<DiscontinueProductModal
product={product}
rerender={props.rerender}
open={discontinueOpen}
onClose={() => setDiscontinueOpen(false)}
/>
{division.hasResearch("Market-TA.I") && ( {division.hasResearch("Market-TA.I") && (
<> <>
<Button onClick={() => setMarketTaOpen(true)}>Market-TA</Button> <Button onClick={() => setMarketTaOpen(true)}>Market-TA</Button>

@ -20,9 +20,9 @@ interface IProps {
export function UnlockUpgrade(props: IProps): React.ReactElement { export function UnlockUpgrade(props: IProps): React.ReactElement {
const corp = useCorporation(); const corp = useCorporation();
const data = props.upgradeData; const data = props.upgradeData;
const tooltip = data[3]; const tooltip = data.desc;
function onClick(): void { function onClick(): void {
if (corp.funds < data[1]) return; if (corp.funds < data.price) return;
try { try {
UU(corp, props.upgradeData); UU(corp, props.upgradeData);
} catch (err) { } catch (err) {
@ -34,11 +34,11 @@ export function UnlockUpgrade(props: IProps): React.ReactElement {
return ( return (
<Grid item xs={4}> <Grid item xs={4}>
<Box display="flex" alignItems="center" flexDirection="row-reverse"> <Box display="flex" alignItems="center" flexDirection="row-reverse">
<Button disabled={corp.funds < data[1]} sx={{ mx: 1 }} onClick={onClick}> <Button disabled={corp.funds < data.price} sx={{ mx: 1 }} onClick={onClick}>
<MoneyCost money={data[1]} corp={corp} /> <MoneyCost money={data.price} corp={corp} />
</Button> </Button>
<Tooltip title={tooltip}> <Tooltip title={tooltip}>
<Typography>{data[2]}</Typography> <Typography>{data.name}</Typography>
</Tooltip> </Tooltip>
</Box> </Box>
</Grid> </Grid>

@ -1,11 +1,11 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../../Faction/Factions";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../../data/Constants";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { use } from "../../ui/Context"; import { use } from "../../../ui/Context";
import { useCorporation } from "./Context"; import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";

@ -1,14 +1,14 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { use } from "../../ui/Context"; import { use } from "../../../ui/Context";
import { useCorporation } from "./Context"; import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import { BuyBackShares } from "../Actions"; import { BuyBackShares } from "../../Actions";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
interface IProps { interface IProps {
open: boolean; open: boolean;

@ -0,0 +1,34 @@
import React from "react";
import { Product } from "../../Product";
import { Modal } from "../../../ui/React/Modal";
import { useDivision } from "../Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
interface IProps {
open: boolean;
onClose: () => void;
product: Product;
rerender: () => void;
}
// Create a popup that lets the player cancel a product
export function CancelProductModal(props: IProps): React.ReactElement {
const division = useDivision();
function cancel(): void {
division.discontinueProduct(props.product);
props.onClose();
props.rerender();
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography>
Are you sure you want to do this? Canceling a product removes it completely and permanently. You will receive no
money back by doing so
</Typography>
<Button onClick={cancel}>Cancel</Button>
</Modal>
);
}

@ -1,8 +1,8 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Money } from "../../ui/React/Money"; import { Money } from "../../../ui/React/Money";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { use } from "../../ui/Context"; import { use } from "../../../ui/Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";

@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import { Product } from "../Product"; import { Product } from "../../Product";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useDivision } from "./Context"; import { useDivision } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";

@ -1,12 +1,12 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Material } from "../Material"; import { Material } from "../../Material";
import { Export } from "../Export"; import { Export } from "../../Export";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../../IIndustry";
import { ExportMaterial } from "../Actions"; import { ExportMaterial } from "../../Actions";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useCorporation } from "./Context"; import { useCorporation } from "../Context";
import { isRelevantMaterial } from "./Helpers"; import { isRelevantMaterial } from "../Helpers";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";

@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../../data/Constants";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useCorporation } from "./Context"; import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";

@ -1,13 +1,13 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { useCorporation } from "./Context"; import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
interface IProps { interface IProps {
open: boolean; open: boolean;

@ -1,13 +1,13 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../../data/Constants";
import { IssueDividends } from "../Actions"; import { IssueDividends } from "../../Actions";
import { useCorporation } from "./Context"; import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
interface IProps { interface IProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;

@ -1,14 +1,14 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../../../utils/helpers/getRandomInt";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../../data/Constants";
import { useCorporation } from "./Context"; import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
interface IEffectTextProps { interface IEffectTextProps {
shares: number | null; shares: number | null;

@ -0,0 +1,53 @@
import React, { useEffect, useState } from "react";
import { LimitMaterialProduction } from "../../Actions";
import { Modal } from "../../../ui/React/Modal";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import { KEY } from "../../../utils/helpers/keyCodes";
import { Material } from "../../Material";
interface IProps {
open: boolean;
onClose: () => void;
material: Material;
}
// Create a popup that lets the player limit the production of a product
export function LimitMaterialProductionModal(props: IProps): React.ReactElement {
const [limit, setLimit] = useState<number | null>(null);
// reset modal internal state on modal close
useEffect(() => {
if (!props.open) {
setLimit(null);
}
}, [props.open]);
function limitMaterialProduction(): void {
let qty = limit;
if (qty === null) qty = -1;
LimitMaterialProduction(props.material, qty);
props.onClose();
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.key === KEY.ENTER) limitMaterialProduction();
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
if (event.target.value === "") setLimit(null);
else setLimit(parseFloat(event.target.value));
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography>
Enter a limit to the amount of this material you would like to produce per second. Leave the box empty to set no
limit.
</Typography>
<TextField autoFocus={true} placeholder="Limit" type="number" onChange={onChange} onKeyDown={onKeyDown} />
<Button onClick={limitMaterialProduction}>Limit production</Button>
</Modal>
);
}

@ -1,11 +1,11 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { Product } from "../Product"; import { Product } from "../../Product";
import { LimitProductProduction } from "../Actions"; import { LimitProductProduction } from "../../Actions";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
interface IProps { interface IProps {
open: boolean; open: boolean;
@ -18,6 +18,13 @@ interface IProps {
export function LimitProductProductionModal(props: IProps): React.ReactElement { export function LimitProductProductionModal(props: IProps): React.ReactElement {
const [limit, setLimit] = useState<number | null>(null); const [limit, setLimit] = useState<number | null>(null);
// reset modal internal state on modal close
useEffect(() => {
if (!props.open) {
setLimit(null);
}
}, [props.open]);
function limitProductProduction(): void { function limitProductProduction(): void {
let qty = limit; let qty = limit;
if (qty === null) qty = -1; if (qty === null) qty = -1;

@ -1,15 +1,15 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { Industries } from "../IndustryData"; import { Industries } from "../../IndustryData";
import { MakeProduct } from "../Actions"; import { MakeProduct } from "../../Actions";
import { useCorporation, useDivision } from "./Context"; import { useCorporation, useDivision } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select"; import Select, { SelectChangeEvent } from "@mui/material/Select";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
interface IProps { interface IProps {
open: boolean; open: boolean;

@ -1,8 +1,8 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { Material } from "../Material"; import { Material } from "../../Material";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useDivision } from "./Context"; import { useDivision } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import FormControlLabel from "@mui/material/FormControlLabel"; import FormControlLabel from "@mui/material/FormControlLabel";

@ -1,8 +1,8 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { Product } from "../Product"; import { Product } from "../../Product";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useDivision } from "./Context"; import { useDivision } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import FormControlLabel from "@mui/material/FormControlLabel"; import FormControlLabel from "@mui/material/FormControlLabel";

@ -1,16 +1,16 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { MaterialSizes } from "../MaterialSizes"; import { MaterialSizes } from "../../MaterialSizes";
import { Warehouse } from "../Warehouse"; import { Warehouse } from "../../Warehouse";
import { Material } from "../Material"; import { Material } from "../../Material";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { BulkPurchase, BuyMaterial } from "../Actions"; import { BulkPurchase, BuyMaterial } from "../../Actions";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useCorporation, useDivision } from "./Context"; import { useCorporation, useDivision } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
interface IBulkPurchaseTextProps { interface IBulkPurchaseTextProps {
warehouse: Warehouse; warehouse: Warehouse;
@ -18,37 +18,6 @@ interface IBulkPurchaseTextProps {
amount: string; amount: string;
} }
function BulkPurchaseText(props: IBulkPurchaseTextProps): React.ReactElement {
const parsedAmt = parseFloat(props.amount);
const cost = parsedAmt * props.mat.bCost;
const matSize = MaterialSizes[props.mat.name];
const maxAmount = (props.warehouse.size - props.warehouse.sizeUsed) / matSize;
if (parsedAmt * matSize > maxAmount) {
return (
<>
<Typography color={"error"}>Not enough warehouse space to purchase this amount</Typography>
</>
);
} else if (isNaN(cost) || parsedAmt < 0) {
return (
<>
<Typography color={"error"}>Invalid put for Bulk Purchase amount</Typography>
</>
);
} else {
return (
<>
<Typography>
Purchasing {numeralWrapper.format(parsedAmt, "0,0.00")} of {props.mat.name} will cost{" "}
{numeralWrapper.formatMoney(cost)}
</Typography>
</>
);
}
}
interface IBPProps { interface IBPProps {
onClose: () => void; onClose: () => void;
mat: Material; mat: Material;
@ -58,6 +27,41 @@ interface IBPProps {
function BulkPurchaseSection(props: IBPProps): React.ReactElement { function BulkPurchaseSection(props: IBPProps): React.ReactElement {
const corp = useCorporation(); const corp = useCorporation();
const [buyAmt, setBuyAmt] = useState(""); const [buyAmt, setBuyAmt] = useState("");
const [disabled, setDisabled] = useState(false);
function BulkPurchaseText(props: IBulkPurchaseTextProps): React.ReactElement {
const parsedAmt = parseFloat(props.amount);
const cost = parsedAmt * props.mat.bCost;
const matSize = MaterialSizes[props.mat.name];
const maxAmount = (props.warehouse.size - props.warehouse.sizeUsed) / matSize;
if (parsedAmt > maxAmount) {
setDisabled(true);
return (
<>
<Typography color={"error"}>Not enough warehouse space to purchase this amount</Typography>
</>
);
} else if (isNaN(cost) || parsedAmt < 0) {
setDisabled(true);
return (
<>
<Typography color={"error"}>Invalid input for Bulk Purchase amount</Typography>
</>
);
} else {
setDisabled(false);
return (
<>
<Typography>
Purchasing {numeralWrapper.format(parsedAmt, "0,0.00")} of {props.mat.name} will cost{" "}
{numeralWrapper.formatMoney(cost)}
</Typography>
</>
);
}
}
function bulkPurchase(): void { function bulkPurchase(): void {
try { try {
@ -90,7 +94,9 @@ function BulkPurchaseSection(props: IBPProps): React.ReactElement {
placeholder="Bulk Purchase amount" placeholder="Bulk Purchase amount"
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
/> />
<Button onClick={bulkPurchase}>Confirm Bulk Purchase</Button> <Button disabled={disabled} onClick={bulkPurchase}>
Confirm Bulk Purchase
</Button>
</> </>
); );
} }

@ -1,13 +1,13 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { IndustryResearchTrees } from "../IndustryData"; import { IndustryResearchTrees } from "../../IndustryData";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../../data/Constants";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../../IIndustry";
import { Research } from "../Actions"; import { Research } from "../../Actions";
import { Node } from "../ResearchTree"; import { Node } from "../../ResearchTree";
import { ResearchMap } from "../ResearchMap"; import { ResearchMap } from "../../ResearchMap";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../../Settings/Settings";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";

@ -1,12 +1,12 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Material } from "../Material"; import { Material } from "../../Material";
import { SellMaterial } from "../Actions"; import { SellMaterial } from "../../Actions";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
function initialPrice(mat: Material): string { function initialPrice(mat: Material): string {
let val = mat.sCost ? mat.sCost + "" : ""; let val = mat.sCost ? mat.sCost + "" : "";

@ -1,15 +1,15 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Product } from "../Product"; import { Product } from "../../Product";
import { SellProduct } from "../Actions"; import { SellProduct } from "../../Actions";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import FormControlLabel from "@mui/material/FormControlLabel"; import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch"; import Switch from "@mui/material/Switch";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
function initialPrice(product: Product): string { function initialPrice(product: Product): string {
let val = product.sCost ? product.sCost + "" : ""; let val = product.sCost ? product.sCost + "" : "";

@ -1,16 +1,16 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { use } from "../../ui/Context"; import { use } from "../../../ui/Context";
import { useCorporation } from "./Context"; import { useCorporation } from "../Context";
import { ICorporation } from "../ICorporation"; import { ICorporation } from "../../ICorporation";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { Money } from "../../ui/React/Money"; import { Money } from "../../../ui/React/Money";
import { SellShares } from "../Actions"; import { SellShares } from "../../Actions";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
interface IProps { interface IProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
@ -33,19 +33,22 @@ export function SellSharesModal(props: IProps): React.ReactElement {
function ProfitIndicator(props: { shares: number | null; corp: ICorporation }): React.ReactElement { function ProfitIndicator(props: { shares: number | null; corp: ICorporation }): React.ReactElement {
if (props.shares === null) return <></>; if (props.shares === null) return <></>;
let text = "";
if (isNaN(props.shares) || props.shares <= 0) { if (isNaN(props.shares) || props.shares <= 0) {
return <>ERROR: Invalid value entered for number of shares to sell</>; text = `ERROR: Invalid value entered for number of shares to sell`;
} else if (props.shares > corp.numShares) { } else if (props.shares > corp.numShares) {
return <>You don't have this many shares to sell!</>; text = `You don't have this many shares to sell!`;
} else { } else {
const stockSaleResults = corp.calculateShareSale(props.shares); const stockSaleResults = corp.calculateShareSale(props.shares);
const profit = stockSaleResults[0]; const profit = stockSaleResults[0];
return ( text = `Sell ${props.shares} shares for a total of ${numeralWrapper.formatMoney(profit)}`;
<>
Sell {props.shares} shares for a total of {numeralWrapper.formatMoney(profit)}
</>
);
} }
return (
<Typography>
<small>{text}</small>
</Typography>
);
} }
function sell(): void { function sell(): void {
@ -84,7 +87,6 @@ export function SellSharesModal(props: IProps): React.ReactElement {
<br /> <br />
The current price of your company's stock is {numeralWrapper.formatMoney(corp.sharePrice)} The current price of your company's stock is {numeralWrapper.formatMoney(corp.sharePrice)}
</Typography> </Typography>
<ProfitIndicator shares={shares} corp={corp} />
<br /> <br />
<TextField <TextField
variant="standard" variant="standard"
@ -97,6 +99,7 @@ export function SellSharesModal(props: IProps): React.ReactElement {
<Button disabled={disabled} onClick={sell} sx={{ mx: 1 }}> <Button disabled={disabled} onClick={sell} sx={{ mx: 1 }}>
Sell shares Sell shares
</Button> </Button>
<ProfitIndicator shares={shares} corp={corp} />
</Modal> </Modal>
); );
} }

@ -1,11 +1,11 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Warehouse } from "../Warehouse"; import { Warehouse } from "../../Warehouse";
import { SetSmartSupply, SetSmartSupplyUseLeftovers } from "../Actions"; import { SetSmartSupply, SetSmartSupplyUseLeftovers } from "../../Actions";
import { Material } from "../Material"; import { Material } from "../../Material";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useDivision } from "./Context"; import { useDivision } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import FormControlLabel from "@mui/material/FormControlLabel"; import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch"; import Switch from "@mui/material/Switch";

@ -1,16 +1,16 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { OfficeSpace } from "../OfficeSpace"; import { OfficeSpace } from "../../OfficeSpace";
import { ThrowParty } from "../Actions"; import { ThrowParty } from "../../Actions";
import { Money } from "../../ui/React/Money"; import { Money } from "../../../ui/React/Money";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useCorporation } from "./Context"; import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
interface IProps { interface IProps {
open: boolean; open: boolean;

@ -1,11 +1,11 @@
import React from "react"; import React from "react";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../../data/Constants";
import { OfficeSpace } from "../OfficeSpace"; import { OfficeSpace } from "../../OfficeSpace";
import { ICorporation } from "../ICorporation"; import { ICorporation } from "../../ICorporation";
import { UpgradeOfficeSize } from "../Actions"; import { UpgradeOfficeSize } from "../../Actions";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useCorporation } from "./Context"; import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";

@ -17,7 +17,7 @@ export function DummyGrid(props: IProps): React.ReactElement {
const ghostGrid = zeros([props.width, props.height]); const ghostGrid = zeros([props.width, props.height]);
return ( return (
<Box> <Box>
<Table> <Table sx={{ width: props.width, height: props.height }}>
<Grid <Grid
width={props.width} width={props.width}
height={props.height} height={props.height}

@ -66,6 +66,7 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
user, Stat Fragments increase the efficacy of adjacent Stat Fragments by 10%, and do not need to be user, Stat Fragments increase the efficacy of adjacent Stat Fragments by 10%, and do not need to be
charged. charged.
</Typography> </Typography>
<br />
<DummyGrid <DummyGrid
width={4} width={4}
@ -86,11 +87,12 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
]} ]}
/> />
<Typography sx={{ fontStyle: "italic" }}> <Typography sx={{ fontStyle: "italic" }}>
This boost provides a bonus to the touching fragment This Booster Fragment provides a bonus to the adjacent Stat Fragment.
</Typography> </Typography>
<br />
<DummyGrid <DummyGrid
width={4} width={3}
height={4} height={4}
fragments={[ fragments={[
new ActiveFragment({ new ActiveFragment({
@ -108,8 +110,10 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
]} ]}
/> />
<Typography sx={{ fontStyle: "italic" }}> <Typography sx={{ fontStyle: "italic" }}>
Even though the booster touches many tiles, the bonus is only applied once. Even though the Booster Fragment touches the Stat Fragment in multiple places, the bonus is only
applied once.
</Typography> </Typography>
<br />
<DummyGrid <DummyGrid
width={4} width={4}
@ -122,16 +126,17 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
fragment: Fragments.find((f) => f.id === 5) ?? Fragments[0], fragment: Fragments.find((f) => f.id === 5) ?? Fragments[0],
}), }),
new ActiveFragment({ new ActiveFragment({
x: 2, x: 1,
y: 0, y: 1,
rotation: 0, rotation: 0,
fragment: Fragments.find((f) => f.id === 105) ?? Fragments[0], fragment: Fragments.find((f) => f.id === 105) ?? Fragments[0],
}), }),
]} ]}
/> />
<Typography sx={{ fontStyle: "italic" }}> <Typography sx={{ fontStyle: "italic" }}>
Even though the booster touches many tiles, the bonus is only applied once. This Booster Fragment does nothing, as it is not touching a Stat Fragment.
</Typography> </Typography>
<br />
<DummyGrid <DummyGrid
width={4} width={4}
@ -158,10 +163,10 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
]} ]}
/> />
<Typography sx={{ fontStyle: "italic" }}> <Typography sx={{ fontStyle: "italic" }}>
This booster provides bonus to all fragment it touches. This Booster Fragment provides a bonus to both Stat Fragments it's touching.
</Typography> </Typography>
<br /> <br />
<Typography> <Typography>
Stat Fragments are charged using the stanek.chargeFragment(rootX, rootY) NetScript API function. The Stat Fragments are charged using the stanek.chargeFragment(rootX, rootY) NetScript API function. The
charging process ordinarily takes 1000ms to complete, but only takes 200ms during bonus time. When the charging process ordinarily takes 1000ms to complete, but only takes 200ms during bonus time. When the

@ -10,6 +10,11 @@ import Button from "@mui/material/Button";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { MenuItem, SelectChangeEvent, TextField, Select } from "@mui/material";
import { Bladeburner } from "../../Bladeburner/Bladeburner";
import { GangConstants } from "../../Gang/data/Constants";
import { FactionNames } from "../../Faction/data/FactionNames";
import { checkForMessagesToSend } from "../../Message/MessageHelpers";
interface IProps { interface IProps {
player: IPlayer; player: IPlayer;
@ -18,6 +23,8 @@ interface IProps {
export function General(props: IProps): React.ReactElement { export function General(props: IProps): React.ReactElement {
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [corporationName, setCorporationName] = useState("");
const [gangFaction, setGangFaction] = useState("");
function addMoney(n: number) { function addMoney(n: number) {
return function () { return function () {
@ -45,6 +52,27 @@ export function General(props: IProps): React.ReactElement {
props.router.toBitVerse(false, false); props.router.toBitVerse(false, false);
} }
function createCorporation(): void {
props.player.startCorporation(corporationName);
}
function joinBladeburner(): void {
props.player.bladeburner = new Bladeburner(props.player);
}
function startGang(): void {
const isHacking = gangFaction === FactionNames.NiteSec || gangFaction === FactionNames.TheBlackHand;
props.player.startGang(gangFaction, isHacking);
}
function setGangFactionDropdown(event: SelectChangeEvent<string>): void {
setGangFaction(event.target.value);
}
function checkMessages(): void {
checkForMessagesToSend();
}
useEffect(() => { useEffect(() => {
if (error) throw new ReferenceError("Manually thrown error"); if (error) throw new ReferenceError("Manually thrown error");
}, [error]); }, [error]);
@ -82,12 +110,29 @@ export function General(props: IProps): React.ReactElement {
</Button> </Button>
<Button onClick={upgradeRam}>+ RAM</Button> <Button onClick={upgradeRam}>+ RAM</Button>
<br /> <br />
<Typography>Corporation Name:</Typography>
<TextField value={corporationName} onChange={(x) => setCorporationName(x.target.value)} />
<Button onClick={createCorporation}>Create Corporation</Button>
<br />
<Typography>Gang Faction:</Typography>
<Select value={gangFaction} onChange={setGangFactionDropdown}>
{GangConstants.Names.map((factionName) => (
<MenuItem key={factionName} value={factionName}>
{factionName}
</MenuItem>
))}
</Select>
<Button onClick={startGang}>Start Gang</Button>
<br />
<Button onClick={joinBladeburner}>Join BladeBurner</Button>
<br />
<Button onClick={quickB1tFlum3}>Quick b1t_flum3.exe</Button> <Button onClick={quickB1tFlum3}>Quick b1t_flum3.exe</Button>
<Button onClick={b1tflum3}>Run b1t_flum3.exe</Button> <Button onClick={b1tflum3}>Run b1t_flum3.exe</Button>
<Button onClick={quickHackW0r1dD43m0n}>Quick w0rld_d34m0n</Button> <Button onClick={quickHackW0r1dD43m0n}>Quick w0rld_d34m0n</Button>
<Button onClick={hackW0r1dD43m0n}>Hack w0rld_d34m0n</Button> <Button onClick={hackW0r1dD43m0n}>Hack w0rld_d34m0n</Button>
<Button onClick={() => setError(true)}>Throw Error</Button> <Button onClick={() => setError(true)}>Throw Error</Button>
<Button onClick={checkMessages}>Check Messages</Button>
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
); );

@ -62,7 +62,8 @@ export function hasAugmentationPrereqs(aug: Augmentation): boolean {
console.error(`Invalid prereq Augmentation ${aug.prereqs[i]}`); console.error(`Invalid prereq Augmentation ${aug.prereqs[i]}`);
continue; continue;
} }
if (prereqAug.owned === false) {
if (Player.hasAugmentation(prereqAug, true) === false) {
hasPrereqs = false; hasPrereqs = false;
// Check if the aug is purchased // Check if the aug is purchased

@ -1,6 +1,8 @@
import React from "react"; import React from "react";
import { IMap } from "../types"; import { IMap } from "../types";
import { FactionNames } from "./data/FactionNames"; import { FactionNames } from "./data/FactionNames";
import { use } from "../ui/Context";
import { Option } from "./ui/Option";
interface FactionInfoParams { interface FactionInfoParams {
infoText?: JSX.Element; infoText?: JSX.Element;
@ -10,6 +12,7 @@ interface FactionInfoParams {
offerSecurityWork?: boolean; offerSecurityWork?: boolean;
special?: boolean; special?: boolean;
keepOnInstall?: boolean; keepOnInstall?: boolean;
assignment?: () => React.ReactElement;
} }
/** /**
@ -51,6 +54,11 @@ export class FactionInfo {
*/ */
special: boolean; special: boolean;
/**
* The data to display on the faction screen.
*/
assignment?: () => React.ReactElement;
constructor(params: FactionInfoParams) { constructor(params: FactionInfoParams) {
this.infoText = params.infoText ?? <></>; this.infoText = params.infoText ?? <></>;
this.enemies = params.enemies ?? []; this.enemies = params.enemies ?? [];
@ -60,6 +68,7 @@ export class FactionInfo {
this.keep = params.keepOnInstall ?? false; this.keep = params.keepOnInstall ?? false;
this.special = params.special ?? false; this.special = params.special ?? false;
this.assignment = params.assignment;
} }
offersWork(): boolean { offersWork(): boolean {
@ -438,11 +447,21 @@ export const FactionInfos: IMap<FactionInfo> = {
), ),
special: true, special: true,
assignment: (): React.ReactElement => {
const router = use.Router();
return (
<Option
buttonText={"Open Bladeburner headquarters"}
infoText={"You can gain reputation with bladeburner by completing contracts and operations."}
onClick={() => router.toBladeburner()}
/>
);
},
}), }),
// prettier-ignore
[FactionNames.ChurchOfTheMachineGod]: new FactionInfo({ [FactionNames.ChurchOfTheMachineGod]: new FactionInfo({
infoText:(<> // prettier-ignore
infoText:(<>
{" `` "}<br /> {" `` "}<br />
{" -odmmNmds: "}<br /> {" -odmmNmds: "}<br />
{" `hNmo:..-omNh. "}<br /> {" `hNmo:..-omNh. "}<br />
@ -472,11 +491,25 @@ export const FactionInfos: IMap<FactionInfo> = {
{" -smNNNNmdo- "}<br /> {" -smNNNNmdo- "}<br />
{" `..` "}<br /><br /> {" `..` "}<br /><br />
Many cultures predict an end to humanity in the near future, a final Many cultures predict an end to humanity in the near future, a final
Armageddon that will end the world; but we disagree. Armageddon that will end the world; but we disagree.</>),
<br /><br />Note that for this faction, reputation can offerHackingWork: false,
only be gained by charging Stanek's gift.</>), offerFieldWork: false,
special: true, offerSecurityWork: false,
keepOnInstall: true, special: true,
keepOnInstall: true,
assignment: (): React.ReactElement => {
const router = use.Router();
return (
<Option
buttonText={"Open Staneks Gift"}
infoText={
"Stanek's Gift is a powerful augmentation that powers up the stat you chose to boost." +
"Gaining reputation with the Church of the Machine God can only be done by charging the gift."
}
onClick={() => router.toStaneksGift()}
/>
);
},
}), }),
// prettier-ignore // prettier-ignore
[FactionNames.Infiltrators]: new FactionInfo({ [FactionNames.Infiltrators]: new FactionInfo({

@ -31,6 +31,17 @@ const useStyles = makeStyles(() =>
}), }),
); );
function DefaultAssignment(): React.ReactElement {
return (
<Typography>
Perform work/carry out assignments for your faction to help further its cause! By doing so you will earn
reputation for your faction. You will also gain reputation passively over time, although at a very slow rate.
Earning reputation will allow you to purchase Augmentations through this faction, which are powerful upgrades that
enhance your abilities.
</Typography>
);
}
export function Info(props: IProps): React.ReactElement { export function Info(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void { function rerender(): void {
@ -44,6 +55,8 @@ export function Info(props: IProps): React.ReactElement {
const classes = useStyles(); const classes = useStyles();
const Assignment = props.factionInfo.assignment ?? DefaultAssignment;
const favorGain = props.faction.getFavorGain(); const favorGain = props.faction.getFavorGain();
const offersWork = const offersWork =
props.factionInfo.offerFieldWork || props.factionInfo.offerSecurityWork || props.factionInfo.offerHackingWork; props.factionInfo.offerFieldWork || props.factionInfo.offerSecurityWork || props.factionInfo.offerHackingWork;
@ -97,18 +110,7 @@ export function Info(props: IProps): React.ReactElement {
</Box> </Box>
<Typography>-------------------------</Typography> <Typography>-------------------------</Typography>
<Typography> <Assignment />
{offersWork ? (
<>
Perform work/carry out assignments for your faction to help further its cause! By doing so you will earn
reputation for your faction.{" "}
</>
) : (
<></>
)}
You will also gain reputation passively over time, although at a very slow rate. Earning reputation will allow
you to purchase Augmentations through this faction, which are powerful upgrades that enhance your abilities.
</Typography>
</> </>
); );
} }

@ -252,6 +252,7 @@ export class Gang implements IGang {
const total = Object.values(AllGangs) const total = Object.values(AllGangs)
.map((g) => g.territory) .map((g) => g.territory)
.reduce((p, c) => p + c, 0); .reduce((p, c) => p + c, 0);
console.log(total);
Object.values(AllGangs).forEach((g) => (g.territory /= total)); Object.values(AllGangs).forEach((g) => (g.territory /= total));
} }
} }

@ -21,12 +21,11 @@ import { iTutorialSteps, iTutorialNextStep, ITutorial } from "../InteractiveTuto
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { GetServer } from "../Server/AllServers"; import { GetServer } from "../Server/AllServers";
import { Server } from "../Server/Server"; import { Server } from "../Server/Server";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
// Returns a boolean indicating whether the player has Hacknet Servers // Returns a boolean indicating whether the player has Hacknet Servers
// (the upgraded form of Hacknet Nodes) // (the upgraded form of Hacknet Nodes)
export function hasHacknetServers(player: IPlayer): boolean { export function hasHacknetServers(player: IPlayer): boolean {
return player.bitNodeN === 9 || SourceFileFlags[9] > 0; return player.bitNodeN === 9 || player.sourceFileLvl(9) > 0;
} }
export function purchaseHacknet(player: IPlayer): number { export function purchaseHacknet(player: IPlayer): number {

@ -15,7 +15,7 @@ import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { Location } from "../Location"; import { Location } from "../Location";
import { CreateCorporationModal } from "../../Corporation/ui/CreateCorporationModal"; import { CreateCorporationModal } from "../../Corporation/ui/modals/CreateCorporationModal";
import { LocationName } from "../data/LocationNames"; import { LocationName } from "../data/LocationNames";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
@ -34,6 +34,7 @@ import { HacknetServer } from "../../Hacknet/HacknetServer";
import { GetServer } from "../../Server/AllServers"; import { GetServer } from "../../Server/AllServers";
import { ArcadeRoot } from "../../Arcade/ui/ArcadeRoot"; import { ArcadeRoot } from "../../Arcade/ui/ArcadeRoot";
import { FactionNames } from "../../Faction/data/FactionNames"; import { FactionNames } from "../../Faction/data/FactionNames";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
type IProps = { type IProps = {
loc: Location; loc: Location;
@ -316,7 +317,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
return renderGrafting(); return renderGrafting();
} }
case LocationName.Sector12CityHall: { case LocationName.Sector12CityHall: {
return <CreateCorporation />; return (BitNodeMultipliers.CorporationSoftCap < 0.15 && <></>) || <CreateCorporation />;
} }
case LocationName.Sector12NSA: { case LocationName.Sector12NSA: {
return renderBladeburner(); return renderBladeburner();

@ -1,31 +1,14 @@
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver"; import { MessageFilenames } from "./MessageHelpers";
export class Message { export class Message {
// Name of Message file // Name of Message file
filename = ""; filename: MessageFilenames;
// The text contains in the Message // The text contains in the Message
msg = ""; msg: string;
// Flag indicating whether this Message has been received by the player constructor(filename: MessageFilenames, msg: string) {
recvd = false;
constructor(filename = "", msg = "") {
this.filename = filename; this.filename = filename;
this.msg = msg; this.msg = msg;
this.recvd = false;
}
// Serialize the current object to a JSON save state
toJSON(): any {
return Generic_toJSON("Message", this);
}
// Initializes a Message Object from a JSON save state
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromJSON(value: any): Message {
return Generic_fromJSON(Message, value.data);
} }
} }
Reviver.constructors.Message = Message;

@ -5,23 +5,23 @@ import { Programs } from "../Programs/Programs";
import { Player } from "../Player"; import { Player } from "../Player";
import { Page } from "../ui/Router"; import { Page } from "../ui/Router";
import { GetServer } from "../Server/AllServers"; import { GetServer } from "../Server/AllServers";
import { SpecialServers } from "../Server/data/SpecialServers";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver } from "../utils/JSONReviver";
import { FactionNames } from "../Faction/data/FactionNames"; import { FactionNames } from "../Faction/data/FactionNames";
import { Server } from "../Server/Server";
//Sends message to player, including a pop up //Sends message to player, including a pop up
function sendMessage(msg: Message, forced = false): void { function sendMessage(msg: Message, forced = false): void {
msg.recvd = true;
if (forced || !Settings.SuppressMessages) { if (forced || !Settings.SuppressMessages) {
showMessage(msg.filename); showMessage(msg.filename);
} }
addMessageToServer(msg, "home"); addMessageToServer(msg);
} }
function showMessage(name: string): void { function showMessage(name: MessageFilenames): void {
const msg = Messages[name]; const msg = Messages[name];
if (!msg) throw new Error("trying to display unexistent message"); if (!(msg instanceof Message)) throw new Error("trying to display unexistent message");
const txt = const txt =
"Message received from unknown sender: <br><br>" + "Message received from unknown sender: <br><br>" +
"<i>" + "<i>" +
@ -34,21 +34,27 @@ function showMessage(name: string): void {
} }
//Adds a message to a server //Adds a message to a server
function addMessageToServer(msg: Message, serverHostname: string): void { function addMessageToServer(msg: Message): void {
const server = GetServer(serverHostname); //Short-circuit if the message has already been saved
if (server == null) { if (recvd(msg)) {
console.warn(`Could not find server ${serverHostname}`);
return; return;
} }
for (let i = 0; i < server.messages.length; ++i) { const server = GetServer("home");
const other = server.messages[i]; if (server == null) {
if (msg.filename === other) { throw new Error("The home server doesn't exist. You done goofed.");
return; //Already exists
}
} }
server.messages.push(msg.filename); server.messages.push(msg.filename);
} }
//Returns whether the given message has already been received
function recvd(msg: Message): boolean {
const server = GetServer("home");
if (server == null) {
throw new Error("The home server doesn't exist. You done goofed.");
}
return server.messages.includes(msg.filename);
}
//Checks if any of the 'timed' messages should be sent //Checks if any of the 'timed' messages should be sent
function checkForMessagesToSend(): void { function checkForMessagesToSend(): void {
if (Router.page() === Page.BitVerse) return; if (Router.page() === Page.BitVerse) return;
@ -60,46 +66,48 @@ function checkForMessagesToSend(): void {
const cybersecTest = Messages[MessageFilenames.CyberSecTest]; const cybersecTest = Messages[MessageFilenames.CyberSecTest];
const nitesecTest = Messages[MessageFilenames.NiteSecTest]; const nitesecTest = Messages[MessageFilenames.NiteSecTest];
const bitrunnersTest = Messages[MessageFilenames.BitRunnersTest]; const bitrunnersTest = Messages[MessageFilenames.BitRunnersTest];
const truthGazer = Messages[MessageFilenames.TruthGazer];
const redpill = Messages[MessageFilenames.RedPill]; const redpill = Messages[MessageFilenames.RedPill];
if (Player.hasAugmentation(AugmentationNames.TheRedPill)) { if (Player.hasAugmentation(AugmentationNames.TheRedPill)) {
//Force the message if the player has never destroyed a BitNode //Get the world daemon required hacking level
sendMessage(redpill, Player.sourceFiles.length === 0); const worldDaemon = GetServer(SpecialServers.WorldDaemon);
} else if (!jumper0.recvd && Player.hacking >= 25) { if (!(worldDaemon instanceof Server)) {
throw new Error("The world daemon is not a server???? Please un-break reality");
}
//If the daemon can be hacked, send the player icarus.msg
if (Player.hacking >= worldDaemon.requiredHackingSkill) {
sendMessage(redpill, Player.sourceFiles.length === 0);
}
//If the daemon cannot be hacked, send the player truthgazer.msg a single time.
else if (!recvd(truthGazer)) {
sendMessage(truthGazer);
}
} else if (!recvd(jumper0) && Player.hacking >= 25) {
sendMessage(jumper0); sendMessage(jumper0);
const flightName = Programs.Flight.name; const flightName = Programs.Flight.name;
const homeComp = Player.getHomeComputer(); const homeComp = Player.getHomeComputer();
if (!homeComp.programs.includes(flightName)) { if (!homeComp.programs.includes(flightName)) {
homeComp.programs.push(flightName); homeComp.programs.push(flightName);
} }
} else if (!jumper1.recvd && Player.hacking >= 40) { } else if (!recvd(jumper1) && Player.hacking >= 40) {
sendMessage(jumper1); sendMessage(jumper1);
} else if (!cybersecTest.recvd && Player.hacking >= 50) { } else if (!recvd(cybersecTest) && Player.hacking >= 50) {
sendMessage(cybersecTest); sendMessage(cybersecTest);
} else if (!jumper2.recvd && Player.hacking >= 175) { } else if (!recvd(jumper2) && Player.hacking >= 175) {
sendMessage(jumper2); sendMessage(jumper2);
} else if (!nitesecTest.recvd && Player.hacking >= 200) { } else if (!recvd(nitesecTest) && Player.hacking >= 200) {
sendMessage(nitesecTest); sendMessage(nitesecTest);
} else if (!jumper3.recvd && Player.hacking >= 350) { } else if (!recvd(jumper3) && Player.hacking >= 350) {
sendMessage(jumper3); sendMessage(jumper3);
} else if (!jumper4.recvd && Player.hacking >= 490) { } else if (!recvd(jumper4) && Player.hacking >= 490) {
sendMessage(jumper4); sendMessage(jumper4);
} else if (!bitrunnersTest.recvd && Player.hacking >= 500) { } else if (!recvd(bitrunnersTest) && Player.hacking >= 500) {
sendMessage(bitrunnersTest); sendMessage(bitrunnersTest);
} }
} }
function AddToAllMessages(msg: Message): void { export enum MessageFilenames {
Messages[msg.filename] = msg;
}
let Messages: { [key: string]: Message } = {};
function loadMessages(saveString: string): void {
Messages = JSON.parse(saveString, Reviver);
}
enum MessageFilenames {
Jumper0 = "j0.msg", Jumper0 = "j0.msg",
Jumper1 = "j1.msg", Jumper1 = "j1.msg",
Jumper2 = "j2.msg", Jumper2 = "j2.msg",
@ -108,105 +116,103 @@ enum MessageFilenames {
CyberSecTest = "csec-test.msg", CyberSecTest = "csec-test.msg",
NiteSecTest = "nitesec-test.msg", NiteSecTest = "nitesec-test.msg",
BitRunnersTest = "19dfj3l1nd.msg", BitRunnersTest = "19dfj3l1nd.msg",
TruthGazer = "truthgazer.msg",
RedPill = "icarus.msg", RedPill = "icarus.msg",
} }
function initMessages(): void { //Reset
//Reset const Messages: Record<MessageFilenames, Message> = {
Messages = {};
//jump3R Messages //jump3R Messages
AddToAllMessages( [MessageFilenames.Jumper0]: new Message(
new Message( MessageFilenames.Jumper0,
MessageFilenames.Jumper0, "I know you can sense it. I know you're searching for it. " +
"I know you can sense it. I know you're searching for it. " + "It's why you spend night after " +
"It's why you spend night after " + "night at your computer. <br><br>It's real, I've seen it. And I can " +
"night at your computer. <br><br>It's real, I've seen it. And I can " + "help you find it. But not right now. You're not ready yet.<br><br>" +
"help you find it. But not right now. You're not ready yet.<br><br>" + "Use this program to track your progress<br><br>" +
"Use this program to track your progress<br><br>" + "The fl1ght.exe program was added to your home computer<br><br>" +
"The fl1ght.exe program was added to your home computer<br><br>" + "-jump3R",
"-jump3R", ),
),
); [MessageFilenames.Jumper1]: new Message(
AddToAllMessages( MessageFilenames.Jumper1,
new Message( `Soon you will be contacted by a hacking group known as ${FactionNames.NiteSec}. ` +
MessageFilenames.Jumper1, "They can help you with your search. <br><br>" +
`Soon you will be contacted by a hacking group known as ${FactionNames.NiteSec}. ` + "You should join them, garner their favor, and " +
"They can help you with your search. <br><br>" + "exploit them for their Augmentations. But do not trust them. " +
"You should join them, garner their favor, and " + "They are not what they seem. No one is.<br><br>" +
"exploit them for their Augmentations. But do not trust them. " + "-jump3R",
"They are not what they seem. No one is.<br><br>" + ),
"-jump3R",
), [MessageFilenames.Jumper2]: new Message(
); MessageFilenames.Jumper2,
AddToAllMessages( "Do not try to save the world. There is no world to save. If " +
new Message( "you want to find the truth, worry only about yourself. Ethics and " +
MessageFilenames.Jumper2, `morals will get you killed. <br><br>Watch out for a hacking group known as ${FactionNames.NiteSec}.` +
"Do not try to save the world. There is no world to save. If " + "<br><br>-jump3R",
"you want to find the truth, worry only about yourself. Ethics and " + ),
`morals will get you killed. <br><br>Watch out for a hacking group known as ${FactionNames.NiteSec}.` +
"<br><br>-jump3R", [MessageFilenames.Jumper3]: new Message(
), MessageFilenames.Jumper3,
); "You must learn to walk before you can run. And you must " +
AddToAllMessages( `run before you can fly. Look for ${FactionNames.TheBlackHand}. <br><br>` +
new Message( "I.I.I.I <br><br>-jump3R",
MessageFilenames.Jumper3, ),
"You must learn to walk before you can run. And you must " +
`run before you can fly. Look for ${FactionNames.TheBlackHand}. <br><br>` + [MessageFilenames.Jumper4]: new Message(
"I.I.I.I <br><br>-jump3R", MessageFilenames.Jumper4,
), "To find what you are searching for, you must understand the bits. " +
); "The bits are all around us. The runners will help you.<br><br>" +
AddToAllMessages( "-jump3R",
new Message( ),
MessageFilenames.Jumper4,
"To find what you are searching for, you must understand the bits. " +
"The bits are all around us. The runners will help you.<br><br>" +
"-jump3R",
),
);
//Messages from hacking factions //Messages from hacking factions
AddToAllMessages( [MessageFilenames.CyberSecTest]: new Message(
new Message( MessageFilenames.CyberSecTest,
MessageFilenames.CyberSecTest, "We've been watching you. Your skills are very impressive. But you're wasting " +
"We've been watching you. Your skills are very impressive. But you're wasting " + "your talents. If you join us, you can put your skills to good use and change " +
"your talents. If you join us, you can put your skills to good use and change " + "the world for the better. If you join us, we can unlock your full potential. <br><br>" +
"the world for the better. If you join us, we can unlock your full potential. <br><br>" + "But first, you must pass our test. Find and install the backdoor on our server. <br><br>" +
"But first, you must pass our test. Find and install the backdoor on our server. <br><br>" + `-${FactionNames.CyberSec}`,
`-${FactionNames.CyberSec}`, ),
),
);
AddToAllMessages(
new Message(
MessageFilenames.NiteSecTest,
"People say that the corrupted governments and corporations rule the world. " +
"Yes, maybe they do. But do you know who everyone really fears? People " +
"like us. Because they can't hide from us. Because they can't fight shadows " +
"and ideas with bullets. <br><br>" +
"Join us, and people will fear you, too. <br><br>" +
"Find and install the backdoor on our server. Then, we will contact you again." +
`<br><br>-${FactionNames.NiteSec}`,
),
);
AddToAllMessages(
new Message(
MessageFilenames.BitRunnersTest,
"We know what you are doing. We know what drives you. We know " +
"what you are looking for. <br><br> " +
"We can help you find the answers.<br><br>" +
"run4theh111z",
),
);
AddToAllMessages( [MessageFilenames.NiteSecTest]: new Message(
new Message( MessageFilenames.NiteSecTest,
MessageFilenames.RedPill, "People say that the corrupted governments and corporations rule the world. " +
"@)(#V%*N)@(#*)*C)@#%*)*V)@#(*%V@)(#VN%*)@#(*%<br>" + "Yes, maybe they do. But do you know who everyone really fears? People " +
")@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)<br>" + "like us. Because they can't hide from us. Because they can't fight shadows " +
"@_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB<br>" + "and ideas with bullets. <br><br>" +
"DFLSMFVMV)#@($*)@#*$MV)@#(*$V)M#(*$)M@(#*VM$)", "Join us, and people will fear you, too. <br><br>" +
), "Find and install the backdoor on our server. Then, we will contact you again." +
); `<br><br>-${FactionNames.NiteSec}`,
} ),
export { Messages, checkForMessagesToSend, showMessage, loadMessages, initMessages }; [MessageFilenames.BitRunnersTest]: new Message(
MessageFilenames.BitRunnersTest,
"We know what you are doing. We know what drives you. We know " +
"what you are looking for. <br><br> " +
"We can help you find the answers.<br><br>" +
"run4theh111z",
),
//Messages to guide players to the daemon
[MessageFilenames.TruthGazer]: new Message(
MessageFilenames.TruthGazer,
//"THE TRUTH CAN NO LONGER ESCAPE YOUR GAZE"
"@&*($#@&__TH3__#@A&#@*)__TRU1H__(*)&*)($#@&()E&R)W&<br>" +
"%@*$^$()@&$)$*@__CAN__()(@^#)@&@)#__N0__(#@&#)@&@&(<br>" +
"*(__LON6ER__^#)@)(()*#@)@__ESCAP3__)#(@(#@*@()@(#*$<br>" +
"()@)#$*%)$#()$#__Y0UR__(*)$#()%(&(%)*!)($__GAZ3__#(",
),
[MessageFilenames.RedPill]: new Message(
MessageFilenames.RedPill,
//"FIND THE-CAVE"
"@)(#V%*N)@(#*)*C)@#%*)*V)@#(*%V@)(#VN%*)@#(*%<br>" +
")@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)<br>" +
"@_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB<br>" +
"DFLSMFVMV)#@($*)@#*$MV)@#(*$V)M#(*$)M@(#*VM$)",
),
};
export { Messages, checkForMessagesToSend, showMessage };

@ -182,6 +182,8 @@ const singularity: IMap<any> = {
installAugmentations: SF4Cost(RamCostConstants.ScriptSingularityFn3RamCost), installAugmentations: SF4Cost(RamCostConstants.ScriptSingularityFn3RamCost),
isFocused: SF4Cost(0.1), isFocused: SF4Cost(0.1),
setFocus: SF4Cost(0.1), setFocus: SF4Cost(0.1),
b1tflum3: SF4Cost(16),
destroyW0r1dD43m0n: SF4Cost(32),
}; };
// Gang API // Gang API
@ -311,6 +313,7 @@ const ui: IMap<any> = {
const grafting: IMap<any> = { const grafting: IMap<any> = {
getAugmentationGraftPrice: 3.75, getAugmentationGraftPrice: 3.75,
getAugmentationGraftTime: 3.75, getAugmentationGraftTime: 3.75,
getGraftableAugmentations: 5,
graftAugmentation: 7.5, graftAugmentation: 7.5,
}; };

@ -34,12 +34,12 @@ import { RunningScript } from "./Script/RunningScript";
import { import {
getServerOnNetwork, getServerOnNetwork,
numCycleForGrowth, numCycleForGrowth,
numCycleForGrowthCorrected,
processSingleServerGrowth, processSingleServerGrowth,
safetlyCreateUniqueServer, safetlyCreateUniqueServer,
} from "./Server/ServerHelpers"; } from "./Server/ServerHelpers";
import { getPurchaseServerCost, getPurchaseServerLimit, getPurchaseServerMaxRam } from "./Server/ServerPurchases"; import { getPurchaseServerCost, getPurchaseServerLimit, getPurchaseServerMaxRam } from "./Server/ServerPurchases";
import { Server } from "./Server/Server"; import { Server } from "./Server/Server";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { influenceStockThroughServerHack, influenceStockThroughServerGrow } from "./StockMarket/PlayerInfluencing"; import { influenceStockThroughServerHack, influenceStockThroughServerGrow } from "./StockMarket/PlayerInfluencing";
import { isValidFilePath, removeLeadingSlash } from "./Terminal/DirectoryHelpers"; import { isValidFilePath, removeLeadingSlash } from "./Terminal/DirectoryHelpers";
@ -638,7 +638,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
if (percentHacked > 0) { if (percentHacked > 0) {
// thread count is limited to the maximum number of threads needed // thread count is limited to the maximum number of threads needed
threads = Math.ceil(1 / percentHacked); threads = Math.min(threads, Math.ceil(1 / percentHacked));
} }
} }
@ -757,9 +757,26 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return numCycleForGrowth(server, Number(growth), Player, cores); return numCycleForGrowth(server, Number(growth), Player, cores);
}, },
growthAnalyzeSecurity: function (_threads: unknown): number { growthAnalyzeSecurity: function (_threads: unknown, _hostname?: unknown, _cores?: unknown): number {
updateDynamicRam("growthAnalyzeSecurity", getRamCost(Player, "growthAnalyzeSecurity")); updateDynamicRam("growthAnalyzeSecurity", getRamCost(Player, "growthAnalyzeSecurity"));
const threads = helper.number("growthAnalyzeSecurity", "threads", _threads); let threads = helper.number("growthAnalyzeSecurity", "threads", _threads);
if (_hostname) {
const cores = helper.number("growthAnalyzeSecurity", "cores", _cores) || 1;
const hostname = helper.string("growthAnalyzeSecurity", "hostname", _hostname);
const server = safeGetServer(hostname, "growthAnalyzeSecurity");
if (!(server instanceof Server)) {
workerScript.log("growthAnalyzeSecurity", () => "Cannot be executed on this server.");
return 0;
}
const maxThreadsNeeded = Math.ceil(
numCycleForGrowthCorrected(server, server.moneyMax, server.moneyAvailable, Player, cores),
);
threads = Math.min(threads, maxThreadsNeeded);
}
return 2 * CONSTANTS.ServerFortifyAmount * threads; return 2 * CONSTANTS.ServerFortifyAmount * threads;
}, },
weaken: async function (_hostname: unknown, { threads: requestedThreads }: BasicHGWOptions = {}): Promise<number> { weaken: async function (_hostname: unknown, { threads: requestedThreads }: BasicHGWOptions = {}): Promise<number> {
@ -1535,7 +1552,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}, },
getBitNodeMultipliers: function (): IBNMults { getBitNodeMultipliers: function (): IBNMults {
updateDynamicRam("getBitNodeMultipliers", getRamCost(Player, "getBitNodeMultipliers")); updateDynamicRam("getBitNodeMultipliers", getRamCost(Player, "getBitNodeMultipliers"));
if (SourceFileFlags[5] <= 0 && Player.bitNodeN !== 5) { if (Player.sourceFileLvl(5) <= 0 && Player.bitNodeN !== 5) {
throw makeRuntimeErrorMsg("getBitNodeMultipliers", "Requires Source-File 5 to run."); throw makeRuntimeErrorMsg("getBitNodeMultipliers", "Requires Source-File 5 to run.");
} }
const copy = Object.assign({}, BitNodeMultipliers); const copy = Object.assign({}, BitNodeMultipliers);

@ -53,6 +53,9 @@ import {
SellShares, SellShares,
BuyBackShares, BuyBackShares,
SetSmartSupplyUseLeftovers, SetSmartSupplyUseLeftovers,
LimitMaterialProduction,
LimitProductProduction,
UpgradeWarehouseCost,
} from "../Corporation/Actions"; } from "../Corporation/Actions";
import { CorporationUnlockUpgrades } from "../Corporation/data/CorporationUnlockUpgrades"; import { CorporationUnlockUpgrades } from "../Corporation/data/CorporationUnlockUpgrades";
import { CorporationUpgrades } from "../Corporation/data/CorporationUpgrades"; import { CorporationUpgrades } from "../Corporation/data/CorporationUpgrades";
@ -64,6 +67,7 @@ import { CorporationConstants } from "../Corporation/data/Constants";
import { IndustryUpgrades } from "../Corporation/IndustryUpgrades"; import { IndustryUpgrades } from "../Corporation/IndustryUpgrades";
import { ResearchMap } from "../Corporation/ResearchMap"; import { ResearchMap } from "../Corporation/ResearchMap";
import { Factions } from "../Faction/Factions"; import { Factions } from "../Faction/Factions";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
export function NetscriptCorporation( export function NetscriptCorporation(
player: IPlayer, player: IPlayer,
@ -74,6 +78,8 @@ export function NetscriptCorporation(
if (!player.canAccessCorporation() || player.hasCorporation()) return false; if (!player.canAccessCorporation() || player.hasCorporation()) return false;
if (!corporationName) return false; if (!corporationName) return false;
if (player.bitNodeN !== 3 && !selfFund) throw new Error("cannot use seed funds outside of BitNode 3"); if (player.bitNodeN !== 3 && !selfFund) throw new Error("cannot use seed funds outside of BitNode 3");
if (BitNodeMultipliers.CorporationSoftCap < 0.15)
throw new Error(`You cannot create a corporation in Bitnode ${player.bitNodeN}`);
if (selfFund) { if (selfFund) {
if (!player.canAfford(150e9)) return false; if (!player.canAfford(150e9)) return false;
@ -88,35 +94,35 @@ export function NetscriptCorporation(
function hasUnlockUpgrade(upgradeName: string): boolean { function hasUnlockUpgrade(upgradeName: string): boolean {
const corporation = getCorporation(); const corporation = getCorporation();
const upgrade = Object.values(CorporationUnlockUpgrades).find((upgrade) => upgrade[2] === upgradeName); const upgrade = Object.values(CorporationUnlockUpgrades).find((upgrade) => upgrade.name === upgradeName);
if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`);
const upgN = upgrade[0]; const upgN = upgrade.index;
return corporation.unlockUpgrades[upgN] === 1; return corporation.unlockUpgrades[upgN] === 1;
} }
function getUnlockUpgradeCost(upgradeName: string): number { function getUnlockUpgradeCost(upgradeName: string): number {
const upgrade = Object.values(CorporationUnlockUpgrades).find((upgrade) => upgrade[2] === upgradeName); const upgrade = Object.values(CorporationUnlockUpgrades).find((upgrade) => upgrade.name === upgradeName);
if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`);
return upgrade[1]; return upgrade.price;
} }
function getUpgradeLevel(_upgradeName: string): number { function getUpgradeLevel(_upgradeName: string): number {
const upgradeName = helper.string("levelUpgrade", "upgradeName", _upgradeName); const upgradeName = helper.string("levelUpgrade", "upgradeName", _upgradeName);
const corporation = getCorporation(); const corporation = getCorporation();
const upgrade = Object.values(CorporationUpgrades).find((upgrade) => upgrade[4] === upgradeName); const upgrade = Object.values(CorporationUpgrades).find((upgrade) => upgrade.name === upgradeName);
if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`);
const upgN = upgrade[0]; const upgN = upgrade.index;
return corporation.upgrades[upgN]; return corporation.upgrades[upgN];
} }
function getUpgradeLevelCost(_upgradeName: string): number { function getUpgradeLevelCost(_upgradeName: string): number {
const upgradeName = helper.string("levelUpgrade", "upgradeName", _upgradeName); const upgradeName = helper.string("levelUpgrade", "upgradeName", _upgradeName);
const corporation = getCorporation(); const corporation = getCorporation();
const upgrade = Object.values(CorporationUpgrades).find((upgrade) => upgrade[4] === upgradeName); const upgrade = Object.values(CorporationUpgrades).find((upgrade) => upgrade.name === upgradeName);
if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`);
const upgN = upgrade[0]; const upgN = upgrade.index;
const baseCost = upgrade[1]; const baseCost = upgrade.basePrice;
const priceMult = upgrade[2]; const priceMult = upgrade.priceMult;
const level = corporation.upgrades[upgN]; const level = corporation.upgrades[upgN];
return baseCost * Math.pow(priceMult, level); return baseCost * Math.pow(priceMult, level);
} }
@ -311,12 +317,16 @@ export function NetscriptCorporation(
checkAccess("getPurchaseWarehouseCost", 7); checkAccess("getPurchaseWarehouseCost", 7);
return CorporationConstants.WarehouseInitialCost; return CorporationConstants.WarehouseInitialCost;
}, },
getUpgradeWarehouseCost: function (_divisionName: unknown, _cityName: unknown): number { getUpgradeWarehouseCost: function (_divisionName: unknown, _cityName: unknown, _amt: unknown = 1): number {
checkAccess("upgradeWarehouse", 7); checkAccess("upgradeWarehouse", 7);
const divisionName = helper.string("getUpgradeWarehouseCost", "divisionName", _divisionName); const divisionName = helper.string("getUpgradeWarehouseCost", "divisionName", _divisionName);
const cityName = helper.city("getUpgradeWarehouseCost", "cityName", _cityName); const cityName = helper.city("getUpgradeWarehouseCost", "cityName", _cityName);
const amt = helper.number("getUpgradeWarehouseCost", "amount", _amt);
if (amt < 1) {
throw helper.makeRuntimeErrorMsg(`corporation.getUpgradeWarehouseCost`, "You must provide a positive number");
}
const warehouse = getWarehouse(divisionName, cityName); const warehouse = getWarehouse(divisionName, cityName);
return CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1); return UpgradeWarehouseCost(warehouse, amt);
}, },
hasWarehouse: function (_divisionName: unknown, _cityName: unknown): boolean { hasWarehouse: function (_divisionName: unknown, _cityName: unknown): boolean {
checkAccess("hasWarehouse", 7); checkAccess("hasWarehouse", 7);
@ -348,6 +358,7 @@ export function NetscriptCorporation(
const material = getMaterial(divisionName, cityName, materialName); const material = getMaterial(divisionName, cityName, materialName);
const corporation = getCorporation(); const corporation = getCorporation();
return { return {
cost: material.bCost,
name: material.name, name: material.name,
qty: material.qty, qty: material.qty,
qlt: material.qlt, qlt: material.qlt,
@ -389,12 +400,16 @@ export function NetscriptCorporation(
const corporation = getCorporation(); const corporation = getCorporation();
PurchaseWarehouse(corporation, getDivision(divisionName), cityName); PurchaseWarehouse(corporation, getDivision(divisionName), cityName);
}, },
upgradeWarehouse: function (_divisionName: unknown, _cityName: unknown): void { upgradeWarehouse: function (_divisionName: unknown, _cityName: unknown, _amt: unknown = 1): void {
checkAccess("upgradeWarehouse", 7); checkAccess("upgradeWarehouse", 7);
const divisionName = helper.string("upgradeWarehouse", "divisionName", _divisionName); const divisionName = helper.string("upgradeWarehouse", "divisionName", _divisionName);
const cityName = helper.city("upgradeWarehouse", "cityName", _cityName); const cityName = helper.city("upgradeWarehouse", "cityName", _cityName);
const amt = helper.number("upgradeWarehouse", "amount", _amt);
const corporation = getCorporation(); const corporation = getCorporation();
UpgradeWarehouse(corporation, getDivision(divisionName), getWarehouse(divisionName, cityName)); if (amt < 1) {
throw helper.makeRuntimeErrorMsg(`corporation.upgradeWarehouse`, "You must provide a positive number");
}
UpgradeWarehouse(corporation, getDivision(divisionName), getWarehouse(divisionName, cityName), amt);
}, },
sellMaterial: function ( sellMaterial: function (
_divisionName: unknown, _divisionName: unknown,
@ -508,6 +523,19 @@ export function NetscriptCorporation(
const corporation = getCorporation(); const corporation = getCorporation();
MakeProduct(corporation, getDivision(divisionName), cityName, productName, designInvest, marketingInvest); MakeProduct(corporation, getDivision(divisionName), cityName, productName, designInvest, marketingInvest);
}, },
limitProductProduction: function (
_divisionName: unknown,
_productName: unknown,
_cityName: unknown,
_qty: unknown,
) {
checkAccess("limitProductProduction", 7);
const divisionName = helper.string("limitProductProduction", "divisionName", _divisionName);
const cityName = helper.city("limitMaterialProduction", "cityName", _cityName);
const productName = helper.string("limitProductProduction", "productName", _productName);
const qty = helper.number("limitMaterialProduction", "qty", _qty);
LimitProductProduction(getProduct(divisionName, productName), cityName, qty);
},
exportMaterial: function ( exportMaterial: function (
_sourceDivision: unknown, _sourceDivision: unknown,
_sourceCity: unknown, _sourceCity: unknown,
@ -548,6 +576,19 @@ export function NetscriptCorporation(
const amt = helper.string("cancelExportMaterial", "amt", _amt); const amt = helper.string("cancelExportMaterial", "amt", _amt);
CancelExportMaterial(targetDivision, targetCity, getMaterial(sourceDivision, sourceCity, materialName), amt + ""); CancelExportMaterial(targetDivision, targetCity, getMaterial(sourceDivision, sourceCity, materialName), amt + "");
}, },
limitMaterialProduction: function (
_divisionName: unknown,
_cityName: unknown,
_materialName: unknown,
_qty: unknown,
) {
checkAccess("limitMaterialProduction", 7);
const divisionName = helper.string("limitMaterialProduction", "divisionName", _divisionName);
const cityName = helper.city("limitMaterialProduction", "cityName", _cityName);
const materialName = helper.string("limitMaterialProduction", "materialName", _materialName);
const qty = helper.number("limitMaterialProduction", "qty", _qty);
LimitMaterialProduction(getMaterial(divisionName, cityName, materialName), qty);
},
setMaterialMarketTA1: function ( setMaterialMarketTA1: function (
_divisionName: unknown, _divisionName: unknown,
_cityName: unknown, _cityName: unknown,
@ -820,7 +861,7 @@ export function NetscriptCorporation(
checkAccess("unlockUpgrade"); checkAccess("unlockUpgrade");
const upgradeName = helper.string("unlockUpgrade", "upgradeName", _upgradeName); const upgradeName = helper.string("unlockUpgrade", "upgradeName", _upgradeName);
const corporation = getCorporation(); const corporation = getCorporation();
const upgrade = Object.values(CorporationUnlockUpgrades).find((upgrade) => upgrade[2] === upgradeName); const upgrade = Object.values(CorporationUnlockUpgrades).find((upgrade) => upgrade.name === upgradeName);
if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`);
UnlockUpgrade(corporation, upgrade); UnlockUpgrade(corporation, upgrade);
}, },
@ -828,7 +869,7 @@ export function NetscriptCorporation(
checkAccess("levelUpgrade"); checkAccess("levelUpgrade");
const upgradeName = helper.string("levelUpgrade", "upgradeName", _upgradeName); const upgradeName = helper.string("levelUpgrade", "upgradeName", _upgradeName);
const corporation = getCorporation(); const corporation = getCorporation();
const upgrade = Object.values(CorporationUpgrades).find((upgrade) => upgrade[4] === upgradeName); const upgrade = Object.values(CorporationUpgrades).find((upgrade) => upgrade.name === upgradeName);
if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`);
LevelUpgrade(corporation, upgrade); LevelUpgrade(corporation, upgrade);
}, },

@ -3,11 +3,13 @@ import { IPlayer } from "../PersonObjects/IPlayer";
import { Exploit } from "../Exploits/Exploit"; import { Exploit } from "../Exploits/Exploit";
import * as bcrypt from "bcryptjs"; import * as bcrypt from "bcryptjs";
import { INetscriptHelper } from "./INetscriptHelper"; import { INetscriptHelper } from "./INetscriptHelper";
import { Apr1Events as devMenu } from "../ui/Apr1";
export interface INetscriptExtra { export interface INetscriptExtra {
heart: { heart: {
break(): number; break(): number;
}; };
openDevMenu(): void;
exploit(): void; exploit(): void;
bypass(doc: Document): void; bypass(doc: Document): void;
alterReality(): void; alterReality(): void;
@ -22,6 +24,9 @@ export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript, help
return player.karma; return player.karma;
}, },
}, },
openDevMenu: function (): void {
devMenu.emit();
},
exploit: function (): void { exploit: function (): void {
player.giveExploit(Exploit.UndocumentedFunctionCall); player.giveExploit(Exploit.UndocumentedFunctionCall);
}, },

@ -4,7 +4,7 @@ import { CityName } from "../Locations/data/CityNames";
import { getRamCost } from "../Netscript/RamCostGenerator"; import { getRamCost } from "../Netscript/RamCostGenerator";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation"; import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation";
import { getAvailableAugs } from "../PersonObjects/Grafting/ui/GraftingRoot"; import { getGraftingAvailableAugs } from "../PersonObjects/Grafting/GraftingHelpers";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { Grafting as IGrafting } from "../ScriptEditor/NetscriptDefinitions"; import { Grafting as IGrafting } from "../ScriptEditor/NetscriptDefinitions";
import { Router } from "../ui/GameRoot"; import { Router } from "../ui/GameRoot";
@ -28,7 +28,7 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
updateRam("getAugmentationGraftPrice"); updateRam("getAugmentationGraftPrice");
const augName = helper.string("getAugmentationGraftPrice", "augName", _augName); const augName = helper.string("getAugmentationGraftPrice", "augName", _augName);
checkGraftingAPIAccess("getAugmentationGraftPrice"); checkGraftingAPIAccess("getAugmentationGraftPrice");
if (!Augmentations.hasOwnProperty(augName)) { if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftPrice", `Invalid aug: ${augName}`); throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftPrice", `Invalid aug: ${augName}`);
} }
const craftableAug = new GraftableAugmentation(Augmentations[augName]); const craftableAug = new GraftableAugmentation(Augmentations[augName]);
@ -39,13 +39,20 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
updateRam("getAugmentationGraftTime"); updateRam("getAugmentationGraftTime");
const augName = helper.string("getAugmentationGraftTime", "augName", _augName); const augName = helper.string("getAugmentationGraftTime", "augName", _augName);
checkGraftingAPIAccess("getAugmentationGraftTime"); checkGraftingAPIAccess("getAugmentationGraftTime");
if (!Augmentations.hasOwnProperty(augName)) { if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftTime", `Invalid aug: ${augName}`); throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftTime", `Invalid aug: ${augName}`);
} }
const craftableAug = new GraftableAugmentation(Augmentations[augName]); const craftableAug = new GraftableAugmentation(Augmentations[augName]);
return craftableAug.time; return craftableAug.time;
}, },
getGraftableAugmentations: (): string[] => {
updateRam("getGraftableAugmentations");
checkGraftingAPIAccess("getGraftableAugmentations");
const graftableAugs = getGraftingAvailableAugs(player);
return graftableAugs;
},
graftAugmentation: (_augName: string, _focus: unknown = true): boolean => { graftAugmentation: (_augName: string, _focus: unknown = true): boolean => {
updateRam("graftAugmentation"); updateRam("graftAugmentation");
const augName = helper.string("graftAugmentation", "augName", _augName); const augName = helper.string("graftAugmentation", "augName", _augName);
@ -57,7 +64,7 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
"You must be in New Tokyo to begin grafting an Augmentation.", "You must be in New Tokyo to begin grafting an Augmentation.",
); );
} }
if (!getAvailableAugs(player).includes(augName)) { if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) {
workerScript.log("grafting.graftAugmentation", () => `Invalid aug: ${augName}`); workerScript.log("grafting.graftAugmentation", () => `Invalid aug: ${augName}`);
return false; return false;
} }

@ -47,6 +47,8 @@ import { Server } from "../Server/Server";
import { netscriptCanHack } from "../Hacking/netscriptCanHack"; import { netscriptCanHack } from "../Hacking/netscriptCanHack";
import { FactionInfos } from "../Faction/FactionInfo"; import { FactionInfos } from "../Faction/FactionInfo";
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper"; import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
import { BlackOperationNames } from "../Bladeburner/data/BlackOperationNames";
import { enterBitNode } from "../RedPill";
export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript): InternalAPI<ISingularity> { export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript): InternalAPI<ISingularity> {
const getAugmentation = function (_ctx: NetscriptContext, name: string): Augmentation { const getAugmentation = function (_ctx: NetscriptContext, name: string): Augmentation {
@ -94,8 +96,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return { return {
getOwnedAugmentations: (_ctx: NetscriptContext) => getOwnedAugmentations: (_ctx: NetscriptContext) =>
function (_purchased: unknown = false): string[] { function (_purchased: unknown = false): string[] {
const purchased = _ctx.helper.boolean(_purchased);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const purchased = _ctx.helper.boolean(_purchased);
const res = []; const res = [];
for (let i = 0; i < player.augmentations.length; ++i) { for (let i = 0; i < player.augmentations.length; ++i) {
res.push(player.augmentations[i].name); res.push(player.augmentations[i].name);
@ -109,52 +111,52 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
getAugmentationsFromFaction: (_ctx: NetscriptContext) => getAugmentationsFromFaction: (_ctx: NetscriptContext) =>
function (_facName: unknown): string[] { function (_facName: unknown): string[] {
const facName = _ctx.helper.string("facName", _facName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
const faction = getFaction(_ctx, facName); const faction = getFaction(_ctx, facName);
return getFactionAugmentationsFiltered(player, faction); return getFactionAugmentationsFiltered(player, faction);
}, },
getAugmentationCost: (_ctx: NetscriptContext) => getAugmentationCost: (_ctx: NetscriptContext) =>
function (_augName: unknown): [number, number] { function (_augName: unknown): [number, number] {
const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName); const aug = getAugmentation(_ctx, augName);
return [aug.baseRepRequirement, aug.baseCost]; return [aug.baseRepRequirement, aug.baseCost];
}, },
getAugmentationPrereq: (_ctx: NetscriptContext) => getAugmentationPrereq: (_ctx: NetscriptContext) =>
function (_augName: unknown): string[] { function (_augName: unknown): string[] {
const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName); const aug = getAugmentation(_ctx, augName);
return aug.prereqs.slice(); return aug.prereqs.slice();
}, },
getAugmentationPrice: (_ctx: NetscriptContext) => getAugmentationPrice: (_ctx: NetscriptContext) =>
function (_augName: unknown): number { function (_augName: unknown): number {
const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName); const aug = getAugmentation(_ctx, augName);
return aug.baseCost; return aug.baseCost;
}, },
getAugmentationRepReq: (_ctx: NetscriptContext) => getAugmentationRepReq: (_ctx: NetscriptContext) =>
function (_augName: unknown): number { function (_augName: unknown): number {
const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName); const aug = getAugmentation(_ctx, augName);
return aug.baseRepRequirement; return aug.baseRepRequirement;
}, },
getAugmentationStats: (_ctx: NetscriptContext) => getAugmentationStats: (_ctx: NetscriptContext) =>
function (_augName: unknown): AugmentationStats { function (_augName: unknown): AugmentationStats {
const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName); const aug = getAugmentation(_ctx, augName);
return Object.assign({}, aug.mults); return Object.assign({}, aug.mults);
}, },
purchaseAugmentation: (_ctx: NetscriptContext) => purchaseAugmentation: (_ctx: NetscriptContext) =>
function (_facName: unknown, _augName: unknown): boolean { function (_facName: unknown, _augName: unknown): boolean {
_ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName); const facName = _ctx.helper.string("facName", _facName);
const augName = _ctx.helper.string("augName", _augName); const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess();
const fac = getFaction(_ctx, facName); const fac = getFaction(_ctx, facName);
const aug = getAugmentation(_ctx, augName); const aug = getAugmentation(_ctx, augName);
@ -200,8 +202,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
softReset: (_ctx: NetscriptContext) => softReset: (_ctx: NetscriptContext) =>
function (_cbScript: unknown = ""): void { function (_cbScript: unknown = ""): void {
const cbScript = _ctx.helper.string("cbScript", _cbScript);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const cbScript = _ctx.helper.string("cbScript", _cbScript);
workerScript.log("softReset", () => "Soft resetting. This will cause this script to be killed"); workerScript.log("softReset", () => "Soft resetting. This will cause this script to be killed");
setTimeout(() => { setTimeout(() => {
@ -215,8 +217,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
installAugmentations: (_ctx: NetscriptContext) => installAugmentations: (_ctx: NetscriptContext) =>
function (_cbScript: unknown = ""): boolean { function (_cbScript: unknown = ""): boolean {
const cbScript = _ctx.helper.string("cbScript", _cbScript);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const cbScript = _ctx.helper.string("cbScript", _cbScript);
if (player.queuedAugmentations.length === 0) { if (player.queuedAugmentations.length === 0) {
workerScript.log("installAugmentations", () => "You do not have any Augmentations to be installed."); workerScript.log("installAugmentations", () => "You do not have any Augmentations to be installed.");
@ -239,8 +241,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
goToLocation: (_ctx: NetscriptContext) => goToLocation: (_ctx: NetscriptContext) =>
function (_locationName: unknown): boolean { function (_locationName: unknown): boolean {
const locationName = _ctx.helper.string("locationName", _locationName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const locationName = _ctx.helper.string("locationName", _locationName);
const location = Object.values(Locations).find((l) => l.name === locationName); const location = Object.values(Locations).find((l) => l.name === locationName);
if (!location) { if (!location) {
workerScript.log("goToLocation", () => `No location named ${locationName}`); workerScript.log("goToLocation", () => `No location named ${locationName}`);
@ -256,10 +258,10 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
universityCourse: (_ctx: NetscriptContext) => universityCourse: (_ctx: NetscriptContext) =>
function (_universityName: unknown, _className: unknown, _focus: unknown = true): boolean { function (_universityName: unknown, _className: unknown, _focus: unknown = true): boolean {
_ctx.helper.checkSingularityAccess();
const universityName = _ctx.helper.string("universityName", _universityName); const universityName = _ctx.helper.string("universityName", _universityName);
const className = _ctx.helper.string("className", _className); const className = _ctx.helper.string("className", _className);
const focus = _ctx.helper.boolean(_focus); const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess();
const wasFocusing = player.focus; const wasFocusing = player.focus;
if (player.isWorking) { if (player.isWorking) {
const txt = player.singularityStopWork(); const txt = player.singularityStopWork();
@ -347,10 +349,10 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
gymWorkout: (_ctx: NetscriptContext) => gymWorkout: (_ctx: NetscriptContext) =>
function (_gymName: unknown, _stat: unknown, _focus: unknown = true): boolean { function (_gymName: unknown, _stat: unknown, _focus: unknown = true): boolean {
_ctx.helper.checkSingularityAccess();
const gymName = _ctx.helper.string("gymName", _gymName); const gymName = _ctx.helper.string("gymName", _gymName);
const stat = _ctx.helper.string("stat", _stat); const stat = _ctx.helper.string("stat", _stat);
const focus = _ctx.helper.boolean(_focus); const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess();
const wasFocusing = player.focus; const wasFocusing = player.focus;
if (player.isWorking) { if (player.isWorking) {
const txt = player.singularityStopWork(); const txt = player.singularityStopWork();
@ -462,8 +464,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
travelToCity: (_ctx: NetscriptContext) => travelToCity: (_ctx: NetscriptContext) =>
function (_cityName: unknown): boolean { function (_cityName: unknown): boolean {
const cityName = _ctx.helper.city("cityName", _cityName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const cityName = _ctx.helper.city("cityName", _cityName);
switch (cityName) { switch (cityName) {
case CityName.Aevum: case CityName.Aevum:
@ -520,8 +522,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
purchaseProgram: (_ctx: NetscriptContext) => purchaseProgram: (_ctx: NetscriptContext) =>
function (_programName: unknown): boolean { function (_programName: unknown): boolean {
const programName = _ctx.helper.string("programName", _programName).toLowerCase();
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const programName = _ctx.helper.string("programName", _programName).toLowerCase();
if (!player.hasTorRouter()) { if (!player.hasTorRouter()) {
workerScript.log("purchaseProgram", () => "You do not have the TOR router."); workerScript.log("purchaseProgram", () => "You do not have the TOR router.");
@ -569,8 +571,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
connect: (_ctx: NetscriptContext) => connect: (_ctx: NetscriptContext) =>
function (_hostname: unknown): boolean { function (_hostname: unknown): boolean {
const hostname = _ctx.helper.string("hostname", _hostname);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const hostname = _ctx.helper.string("hostname", _hostname);
if (!hostname) { if (!hostname) {
throw _ctx.helper.makeRuntimeErrorMsg(`Invalid hostname: '${hostname}'`); throw _ctx.helper.makeRuntimeErrorMsg(`Invalid hostname: '${hostname}'`);
} }
@ -580,6 +582,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
throw _ctx.helper.makeRuntimeErrorMsg(`Invalid hostname: '${hostname}'`); throw _ctx.helper.makeRuntimeErrorMsg(`Invalid hostname: '${hostname}'`);
} }
//Home case
if (hostname === "home") { if (hostname === "home") {
player.getCurrentServer().isConnectedTo = false; player.getCurrentServer().isConnectedTo = false;
player.currentServer = player.getHomeComputer().hostname; player.currentServer = player.getHomeComputer().hostname;
@ -588,6 +591,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return true; return true;
} }
//Adjacent server case
const server = player.getCurrentServer(); const server = player.getCurrentServer();
for (let i = 0; i < server.serversOnNetwork.length; i++) { for (let i = 0; i < server.serversOnNetwork.length; i++) {
const other = getServerOnNetwork(server, i); const other = getServerOnNetwork(server, i);
@ -601,6 +605,17 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
} }
} }
//Backdoor case
const other = GetServer(hostname);
if (other !== null && other instanceof Server && other.backdoorInstalled) {
player.getCurrentServer().isConnectedTo = false;
player.currentServer = target.hostname;
player.getCurrentServer().isConnectedTo = true;
Terminal.setcwd("/");
return true;
}
//Failure case
return false; return false;
}, },
manualHack: (_ctx: NetscriptContext) => manualHack: (_ctx: NetscriptContext) =>
@ -649,8 +664,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
setFocus: (_ctx: NetscriptContext) => setFocus: (_ctx: NetscriptContext) =>
function (_focus: unknown): boolean { function (_focus: unknown): boolean {
const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const focus = _ctx.helper.boolean(_focus);
if (!player.isWorking) { if (!player.isWorking) {
throw _ctx.helper.makeRuntimeErrorMsg("Not currently working"); throw _ctx.helper.makeRuntimeErrorMsg("Not currently working");
} }
@ -849,9 +864,9 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
workForCompany: (_ctx: NetscriptContext) => workForCompany: (_ctx: NetscriptContext) =>
function (_companyName: unknown, _focus: unknown = true): boolean { function (_companyName: unknown, _focus: unknown = true): boolean {
_ctx.helper.checkSingularityAccess();
let companyName = _ctx.helper.string("companyName", _companyName); let companyName = _ctx.helper.string("companyName", _companyName);
const focus = _ctx.helper.boolean(_focus); const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess();
// Sanitize input // Sanitize input
if (companyName == null) { if (companyName == null) {
@ -905,9 +920,9 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
applyToCompany: (_ctx: NetscriptContext) => applyToCompany: (_ctx: NetscriptContext) =>
function (_companyName: unknown, _field: unknown): boolean { function (_companyName: unknown, _field: unknown): boolean {
_ctx.helper.checkSingularityAccess();
const companyName = _ctx.helper.string("companyName", _companyName); const companyName = _ctx.helper.string("companyName", _companyName);
const field = _ctx.helper.string("field", _field); const field = _ctx.helper.string("field", _field);
_ctx.helper.checkSingularityAccess();
getCompany(_ctx, companyName); getCompany(_ctx, companyName);
player.location = companyName as LocationName; player.location = companyName as LocationName;
@ -977,22 +992,22 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
getCompanyRep: (_ctx: NetscriptContext) => getCompanyRep: (_ctx: NetscriptContext) =>
function (_companyName: unknown): number { function (_companyName: unknown): number {
const companyName = _ctx.helper.string("companyName", _companyName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const companyName = _ctx.helper.string("companyName", _companyName);
const company = getCompany(_ctx, companyName); const company = getCompany(_ctx, companyName);
return company.playerReputation; return company.playerReputation;
}, },
getCompanyFavor: (_ctx: NetscriptContext) => getCompanyFavor: (_ctx: NetscriptContext) =>
function (_companyName: unknown): number { function (_companyName: unknown): number {
const companyName = _ctx.helper.string("companyName", _companyName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const companyName = _ctx.helper.string("companyName", _companyName);
const company = getCompany(_ctx, companyName); const company = getCompany(_ctx, companyName);
return company.favor; return company.favor;
}, },
getCompanyFavorGain: (_ctx: NetscriptContext) => getCompanyFavorGain: (_ctx: NetscriptContext) =>
function (_companyName: unknown): number { function (_companyName: unknown): number {
const companyName = _ctx.helper.string("companyName", _companyName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const companyName = _ctx.helper.string("companyName", _companyName);
const company = getCompany(_ctx, companyName); const company = getCompany(_ctx, companyName);
return company.getFavorGain(); return company.getFavorGain();
}, },
@ -1004,8 +1019,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
joinFaction: (_ctx: NetscriptContext) => joinFaction: (_ctx: NetscriptContext) =>
function (_facName: unknown): boolean { function (_facName: unknown): boolean {
const facName = _ctx.helper.string("facName", _facName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
getFaction(_ctx, facName); getFaction(_ctx, facName);
if (!player.factionInvitations.includes(facName)) { if (!player.factionInvitations.includes(facName)) {
@ -1028,10 +1043,10 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
workForFaction: (_ctx: NetscriptContext) => workForFaction: (_ctx: NetscriptContext) =>
function (_facName: unknown, _type: unknown, _focus: unknown = true): boolean { function (_facName: unknown, _type: unknown, _focus: unknown = true): boolean {
_ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName); const facName = _ctx.helper.string("facName", _facName);
const type = _ctx.helper.string("type", _type); const type = _ctx.helper.string("type", _type);
const focus = _ctx.helper.boolean(_focus); const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess();
getFaction(_ctx, facName); getFaction(_ctx, facName);
// if the player is in a gang and the target faction is any of the gang faction, fail // if the player is in a gang and the target faction is any of the gang faction, fail
@ -1117,30 +1132,30 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
getFactionRep: (_ctx: NetscriptContext) => getFactionRep: (_ctx: NetscriptContext) =>
function (_facName: unknown): number { function (_facName: unknown): number {
const facName = _ctx.helper.string("facName", _facName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
const faction = getFaction(_ctx, facName); const faction = getFaction(_ctx, facName);
return faction.playerReputation; return faction.playerReputation;
}, },
getFactionFavor: (_ctx: NetscriptContext) => getFactionFavor: (_ctx: NetscriptContext) =>
function (_facName: unknown): number { function (_facName: unknown): number {
const facName = _ctx.helper.string("facName", _facName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
const faction = getFaction(_ctx, facName); const faction = getFaction(_ctx, facName);
return faction.favor; return faction.favor;
}, },
getFactionFavorGain: (_ctx: NetscriptContext) => getFactionFavorGain: (_ctx: NetscriptContext) =>
function (_facName: unknown): number { function (_facName: unknown): number {
const facName = _ctx.helper.string("facName", _facName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
const faction = getFaction(_ctx, facName); const faction = getFaction(_ctx, facName);
return faction.getFavorGain(); return faction.getFavorGain();
}, },
donateToFaction: (_ctx: NetscriptContext) => donateToFaction: (_ctx: NetscriptContext) =>
function (_facName: unknown, _amt: unknown): boolean { function (_facName: unknown, _amt: unknown): boolean {
_ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName); const facName = _ctx.helper.string("facName", _facName);
const amt = _ctx.helper.number("amt", _amt); const amt = _ctx.helper.number("amt", _amt);
_ctx.helper.checkSingularityAccess();
const faction = getFaction(_ctx, facName); const faction = getFaction(_ctx, facName);
if (!player.factions.includes(faction.name)) { if (!player.factions.includes(faction.name)) {
workerScript.log("donateToFaction", () => `You can't donate to '${facName}' because you aren't a member`); workerScript.log("donateToFaction", () => `You can't donate to '${facName}' because you aren't a member`);
@ -1187,9 +1202,9 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
createProgram: (_ctx: NetscriptContext) => createProgram: (_ctx: NetscriptContext) =>
function (_programName: unknown, _focus: unknown = true): boolean { function (_programName: unknown, _focus: unknown = true): boolean {
_ctx.helper.checkSingularityAccess();
const programName = _ctx.helper.string("programName", _programName).toLowerCase(); const programName = _ctx.helper.string("programName", _programName).toLowerCase();
const focus = _ctx.helper.boolean(_focus); const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess();
const wasFocusing = player.focus; const wasFocusing = player.focus;
if (player.isWorking) { if (player.isWorking) {
@ -1236,8 +1251,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
commitCrime: (_ctx: NetscriptContext) => commitCrime: (_ctx: NetscriptContext) =>
function (_crimeRoughName: unknown): number { function (_crimeRoughName: unknown): number {
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
if (player.isWorking) { if (player.isWorking) {
const txt = player.singularityStopWork(); const txt = player.singularityStopWork();
@ -1257,8 +1272,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
getCrimeChance: (_ctx: NetscriptContext) => getCrimeChance: (_ctx: NetscriptContext) =>
function (_crimeRoughName: unknown): number { function (_crimeRoughName: unknown): number {
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
const crime = findCrime(crimeRoughName.toLowerCase()); const crime = findCrime(crimeRoughName.toLowerCase());
if (crime == null) { if (crime == null) {
@ -1269,8 +1284,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
getCrimeStats: (_ctx: NetscriptContext) => getCrimeStats: (_ctx: NetscriptContext) =>
function (_crimeRoughName: unknown): CrimeStats { function (_crimeRoughName: unknown): CrimeStats {
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
const crime = findCrime(crimeRoughName.toLowerCase()); const crime = findCrime(crimeRoughName.toLowerCase());
if (crime == null) { if (crime == null) {
@ -1292,8 +1307,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}, },
getDarkwebProgramCost: (_ctx: NetscriptContext) => getDarkwebProgramCost: (_ctx: NetscriptContext) =>
function (_programName: unknown): number { function (_programName: unknown): number {
const programName = _ctx.helper.string("programName", _programName).toLowerCase();
_ctx.helper.checkSingularityAccess(); _ctx.helper.checkSingularityAccess();
const programName = _ctx.helper.string("programName", _programName).toLowerCase();
// If we don't have Tor, log it and return -1 // If we don't have Tor, log it and return -1
if (!player.hasTorRouter()) { if (!player.hasTorRouter()) {
@ -1322,5 +1337,51 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
} }
return item.price; return item.price;
}, },
b1tflum3:
(_ctx: NetscriptContext) =>
(_nextBN: unknown, _callbackScript: unknown = ""): void => {
_ctx.helper.checkSingularityAccess();
const nextBN = _ctx.helper.number("nextBN", _nextBN);
const callbackScript = _ctx.helper.string("callbackScript", _callbackScript);
_ctx.helper.checkSingularityAccess();
enterBitNode(Router, true, player.bitNodeN, nextBN);
if (callbackScript)
setTimeout(() => {
runAfterReset(callbackScript);
}, 0);
},
destroyW0r1dD43m0n:
(_ctx: NetscriptContext) =>
(_nextBN: unknown, _callbackScript: unknown = ""): void => {
_ctx.helper.checkSingularityAccess();
const nextBN = _ctx.helper.number("nextBN", _nextBN);
const callbackScript = _ctx.helper.string("callbackScript", _callbackScript);
_ctx.helper.checkSingularityAccess();
const hackingRequirements = (): boolean => {
const wd = GetServer(SpecialServers.WorldDaemon);
if (!(wd instanceof Server))
throw new Error("WorldDaemon was not a normal server. This is a bug contact dev.");
if (player.hacking < wd.requiredHackingSkill) return false;
if (!wd.hasAdminRights) return false;
return true;
};
const bladeburnerRequirements = (): boolean => {
if (!player.inBladeburner()) return false;
if (!player.bladeburner) return false;
return player.bladeburner.blackops[BlackOperationNames.OperationDaedalus];
};
if (!hackingRequirements() && !bladeburnerRequirements()) {
_ctx.log(() => "Requirements not met to destroy the world daemon");
return;
}
enterBitNode(Router, false, player.bitNodeN, nextBN);
if (callbackScript)
setTimeout(() => {
runAfterReset(callbackScript);
}, 0);
},
}; };
} }

@ -2,7 +2,6 @@ import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator"; import { getRamCost } from "../Netscript/RamCostGenerator";
import { FactionWorkType } from "../Faction/FactionWorkTypeEnum"; import { FactionWorkType } from "../Faction/FactionWorkTypeEnum";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum"; import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers"; import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers";
@ -20,7 +19,7 @@ import {
export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): ISleeve { export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): ISleeve {
const checkSleeveAPIAccess = function (func: string): void { const checkSleeveAPIAccess = function (func: string): void {
if (player.bitNodeN !== 10 && !SourceFileFlags[10]) { if (player.bitNodeN !== 10 && !player.sourceFileLvl(10)) {
throw helper.makeRuntimeErrorMsg( throw helper.makeRuntimeErrorMsg(
`sleeve.${func}`, `sleeve.${func}`,
"You do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10", "You do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10",

@ -0,0 +1,15 @@
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { IPlayer } from "../IPlayer";
export const getGraftingAvailableAugs = (player: IPlayer): string[] => {
const augs: string[] = [];
for (const [augName, aug] of Object.entries(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor || augName === AugmentationNames.TheRedPill || aug.isSpecial)
continue;
augs.push(augName);
}
return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation));
};

@ -15,22 +15,11 @@ import { ConfirmationModal } from "../../../ui/React/ConfirmationModal";
import { Money } from "../../../ui/React/Money"; import { Money } from "../../../ui/React/Money";
import { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions";
import { IPlayer } from "../../IPlayer"; import { IPlayer } from "../../IPlayer";
import { getGraftingAvailableAugs } from "../GraftingHelpers";
import { GraftableAugmentation } from "../GraftableAugmentation"; import { GraftableAugmentation } from "../GraftableAugmentation";
const GraftableAugmentations: IMap<GraftableAugmentation> = {}; const GraftableAugmentations: IMap<GraftableAugmentation> = {};
export const getAvailableAugs = (player: IPlayer): string[] => {
const augs: string[] = [];
for (const [augName, aug] of Object.entries(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor || augName === AugmentationNames.TheRedPill || aug.isSpecial)
continue;
augs.push(augName);
}
return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation));
};
const canGraft = (player: IPlayer, aug: GraftableAugmentation): boolean => { const canGraft = (player: IPlayer, aug: GraftableAugmentation): boolean => {
if (player.money < aug.cost) { if (player.money < aug.cost) {
return false; return false;
@ -71,7 +60,7 @@ export const GraftingRoot = (): React.ReactElement => {
GraftableAugmentations[name] = graftableAug; GraftableAugmentations[name] = graftableAug;
} }
const [selectedAug, setSelectedAug] = useState(getAvailableAugs(player)[0]); const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]);
const [graftOpen, setGraftOpen] = useState(false); const [graftOpen, setGraftOpen] = useState(false);
return ( return (
@ -92,10 +81,10 @@ export const GraftingRoot = (): React.ReactElement => {
<Box sx={{ my: 3 }}> <Box sx={{ my: 3 }}>
<Typography variant="h5">Graft Augmentations</Typography> <Typography variant="h5">Graft Augmentations</Typography>
{getAvailableAugs(player).length > 0 ? ( {getGraftingAvailableAugs(player).length > 0 ? (
<Paper sx={{ my: 1, width: "fit-content", display: "grid", gridTemplateColumns: "1fr 3fr" }}> <Paper sx={{ my: 1, width: "fit-content", display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<List sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}> <List sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}>
{getAvailableAugs(player).map((k, i) => ( {getGraftingAvailableAugs(player).map((k, i) => (
<ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}> <ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
<Typography>{k}</Typography> <Typography>{k}</Typography>
</ListItemButton> </ListItemButton>

@ -7,7 +7,7 @@ import { Augmentation } from "../../Augmentation/Augmentation";
import { calculateEntropy } from "../Grafting/EntropyAccumulation"; import { calculateEntropy } from "../Grafting/EntropyAccumulation";
export function hasAugmentation(this: IPlayer, aug: string | Augmentation, installed = false): boolean { export function hasAugmentation(this: IPlayer, aug: string | Augmentation, includeQueued = false): boolean {
const augName: string = aug instanceof Augmentation ? aug.name : aug; const augName: string = aug instanceof Augmentation ? aug.name : aug;
for (const owned of this.augmentations) { for (const owned of this.augmentations) {
@ -16,7 +16,7 @@ export function hasAugmentation(this: IPlayer, aug: string | Augmentation, insta
} }
} }
if (!installed) { if (!includeQueued) {
for (const owned of this.queuedAugmentations) { for (const owned of this.queuedAugmentations) {
if (owned.name === augName) { if (owned.name === augName) {
return true; return true;

@ -1,5 +1,4 @@
import { Bladeburner } from "../../Bladeburner/Bladeburner"; import { Bladeburner } from "../../Bladeburner/Bladeburner";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
export function canAccessBladeburner(this: IPlayer): boolean { export function canAccessBladeburner(this: IPlayer): boolean {
@ -7,7 +6,7 @@ export function canAccessBladeburner(this: IPlayer): boolean {
return false; return false;
} }
return this.bitNodeN === 6 || this.bitNodeN === 7 || SourceFileFlags[6] > 0 || SourceFileFlags[7] > 0; return this.bitNodeN === 6 || this.bitNodeN === 7 || this.sourceFileLvl(6) > 0 || this.sourceFileLvl(7) > 0;
} }
export function inBladeburner(this: IPlayer): boolean { export function inBladeburner(this: IPlayer): boolean {

@ -1,10 +1,12 @@
import { Corporation } from "../../Corporation/Corporation"; import { Corporation } from "../../Corporation/Corporation";
import { CorporationUnlockUpgrades } from "../../Corporation/data/CorporationUnlockUpgrades"; import {
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; CorporationUnlockUpgradeIndex,
CorporationUnlockUpgrades,
} from "../../Corporation/data/CorporationUnlockUpgrades";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
export function canAccessCorporation(this: IPlayer): boolean { export function canAccessCorporation(this: IPlayer): boolean {
return this.bitNodeN === 3 || SourceFileFlags[3] > 0; return this.bitNodeN === 3 || this.sourceFileLvl(3) > 0;
} }
export function hasCorporation(this: IPlayer): boolean { export function hasCorporation(this: IPlayer): boolean {
@ -19,9 +21,9 @@ export function startCorporation(this: IPlayer, corpName: string, additionalShar
name: corpName, name: corpName,
}); });
if (SourceFileFlags[3] === 3) { if (this.sourceFileLvl(3) === 3) {
const warehouseApi = CorporationUnlockUpgrades["7"][0]; const warehouseApi = CorporationUnlockUpgrades[CorporationUnlockUpgradeIndex.WarehouseAPI].index;
const OfficeApi = CorporationUnlockUpgrades["8"][0]; const OfficeApi = CorporationUnlockUpgrades[CorporationUnlockUpgradeIndex.OfficeAPI].index;
this.corporation.unlockUpgrades[warehouseApi] = 1; this.corporation.unlockUpgrades[warehouseApi] = 1;
this.corporation.unlockUpgrades[OfficeApi] = 1; this.corporation.unlockUpgrades[OfficeApi] = 1;

@ -1,7 +1,6 @@
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { Gang } from "../../Gang/Gang"; import { Gang } from "../../Gang/Gang";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
// Amount of negative karma needed to manage a gang in BitNodes other than 2 // Amount of negative karma needed to manage a gang in BitNodes other than 2
@ -11,7 +10,7 @@ export function canAccessGang(this: IPlayer): boolean {
if (this.bitNodeN === 2) { if (this.bitNodeN === 2) {
return true; return true;
} }
if (SourceFileFlags[2] <= 0) { if (this.sourceFileLvl(2) <= 0) {
return false; return false;
} }

@ -45,7 +45,6 @@ import { SpecialServers } from "../../Server/data/SpecialServers";
import { applySourceFile } from "../../SourceFile/applySourceFile"; import { applySourceFile } from "../../SourceFile/applySourceFile";
import { applyExploit } from "../../Exploits/applyExploits"; import { applyExploit } from "../../Exploits/applyExploits";
import { SourceFiles } from "../../SourceFile/SourceFiles"; import { SourceFiles } from "../../SourceFile/SourceFiles";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { influenceStockThroughCompanyWork } from "../../StockMarket/PlayerInfluencing"; import { influenceStockThroughCompanyWork } from "../../StockMarket/PlayerInfluencing";
import { getHospitalizationCost } from "../../Hospital/Hospital"; import { getHospitalizationCost } from "../../Hospital/Hospital";
import { WorkerScript } from "../../Netscript/WorkerScript"; import { WorkerScript } from "../../Netscript/WorkerScript";
@ -121,7 +120,7 @@ export function prestigeAugmentation(this: PlayerObject): void {
this.queuedAugmentations = []; this.queuedAugmentations = [];
const numSleeves = Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant; const numSleeves = Math.min(3, this.sourceFileLvl(10) + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant;
if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves; if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves;
for (let i = this.sleeves.length; i < numSleeves; i++) { for (let i = this.sleeves.length; i < numSleeves; i++) {
this.sleeves.push(new Sleeve(this)); this.sleeves.push(new Sleeve(this));
@ -475,7 +474,7 @@ export function gainIntelligenceExp(this: IPlayer, exp: number): void {
console.error("ERROR: NaN passed into Player.gainIntelligenceExp()"); console.error("ERROR: NaN passed into Player.gainIntelligenceExp()");
return; return;
} }
if (SourceFileFlags[5] > 0 || this.intelligence > 0) { if (this.sourceFileLvl(5) > 0 || this.intelligence > 0) {
this.intelligence_exp += exp; this.intelligence_exp += exp;
this.intelligence = Math.floor(this.calculateSkill(this.intelligence_exp)); this.intelligence = Math.floor(this.calculateSkill(this.intelligence_exp));
} }
@ -1045,7 +1044,7 @@ export function getWorkMoneyGain(this: IPlayer): number {
// If player has SF-11, calculate salary multiplier from favor // If player has SF-11, calculate salary multiplier from favor
let bn11Mult = 1; let bn11Mult = 1;
const company = Companies[this.companyName]; const company = Companies[this.companyName];
if (SourceFileFlags[11] > 0) { if (this.sourceFileLvl(11) > 0) {
bn11Mult = 1 + company.favor / 100; bn11Mult = 1 + company.favor / 100;
} }
@ -1323,20 +1322,21 @@ export function createProgramWork(this: IPlayer, numCycles: number): boolean {
export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): string { export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): string {
const programName = this.createProgramName; const programName = this.createProgramName;
if (cancelled === false) { if (!cancelled) {
//Complete case
this.gainIntelligenceExp((CONSTANTS.IntelligenceProgramBaseExpGain * this.timeWorked) / 1000);
dialogBoxCreate(`You've finished creating ${programName}!<br>The new program can be found on your home computer.`); dialogBoxCreate(`You've finished creating ${programName}!<br>The new program can be found on your home computer.`);
this.getHomeComputer().programs.push(programName); if (!this.getHomeComputer().programs.includes(programName)) {
} else { this.getHomeComputer().programs.push(programName);
}
} else if (!this.getHomeComputer().programs.includes(programName)) {
//Incomplete case
const perc = (Math.floor((this.timeWorkedCreateProgram / this.timeNeededToCompleteWork) * 10000) / 100).toString(); const perc = (Math.floor((this.timeWorkedCreateProgram / this.timeNeededToCompleteWork) * 10000) / 100).toString();
const incompleteName = programName + "-" + perc + "%-INC"; const incompleteName = programName + "-" + perc + "%-INC";
this.getHomeComputer().programs.push(incompleteName); this.getHomeComputer().programs.push(incompleteName);
} }
if (!cancelled) {
this.gainIntelligenceExp((CONSTANTS.IntelligenceProgramBaseExpGain * this.timeWorked) / 1000);
}
this.isWorking = false; this.isWorking = false;
this.resetWorkStatus(); this.resetWorkStatus();
@ -2092,12 +2092,7 @@ export function reapplyAllAugmentations(this: IPlayer, resetMultipliers = true):
const playerAug = this.augmentations[i]; const playerAug = this.augmentations[i];
const augName = playerAug.name; const augName = playerAug.name;
const aug = Augmentations[augName];
if (aug == null) {
console.warn(`Invalid augmentation name in Player.reapplyAllAugmentations(). Aug ${augName} will be skipped`);
continue;
}
aug.owned = true;
if (augName == AugmentationNames.NeuroFluxGovernor) { if (augName == AugmentationNames.NeuroFluxGovernor) {
for (let j = 0; j < playerAug.level; ++j) { for (let j = 0; j < playerAug.level; ++j) {
applyAugmentation(this.augmentations[i], true); applyAugmentation(this.augmentations[i], true);
@ -2718,7 +2713,7 @@ export function gotoLocation(this: IPlayer, to: LocationName): boolean {
} }
export function canAccessGrafting(this: IPlayer): boolean { export function canAccessGrafting(this: IPlayer): boolean {
return this.bitNodeN === 10 || SourceFileFlags[10] > 0; return this.bitNodeN === 10 || this.sourceFileLvl(10) > 0;
} }
export function giveExploit(this: IPlayer, exploit: Exploit): void { export function giveExploit(this: IPlayer, exploit: Exploit): void {
@ -2756,7 +2751,7 @@ export function setMult(this: IPlayer, name: string, mult: number): void {
} }
export function canAccessCotMG(this: IPlayer): boolean { export function canAccessCotMG(this: IPlayer): boolean {
return this.bitNodeN === 13 || SourceFileFlags[13] > 0; return this.bitNodeN === 13 || this.sourceFileLvl(13) > 0;
} }
export function sourceFileLvl(this: IPlayer, n: number): number { export function sourceFileLvl(this: IPlayer, n: number): number {

@ -12,7 +12,6 @@ import { Faction } from "./Faction/Faction";
import { Factions, initFactions } from "./Faction/Factions"; import { Factions, initFactions } from "./Faction/Factions";
import { joinFaction } from "./Faction/FactionHelpers"; import { joinFaction } from "./Faction/FactionHelpers";
import { updateHashManagerCapacity } from "./Hacknet/HacknetHelpers"; import { updateHashManagerCapacity } from "./Hacknet/HacknetHelpers";
import { initMessages } from "./Message/MessageHelpers";
import { prestigeWorkerScripts } from "./NetscriptWorker"; import { prestigeWorkerScripts } from "./NetscriptWorker";
import { Player } from "./Player"; import { Player } from "./Player";
import { Router } from "./ui/GameRoot"; import { Router } from "./ui/GameRoot";
@ -21,7 +20,6 @@ import { LiteratureNames } from "./Literature/data/LiteratureNames";
import { GetServer, AddToAllServers, initForeignServers, prestigeAllServers } from "./Server/AllServers"; import { GetServer, AddToAllServers, initForeignServers, prestigeAllServers } from "./Server/AllServers";
import { prestigeHomeComputer } from "./Server/ServerHelpers"; import { prestigeHomeComputer } from "./Server/ServerHelpers";
import { SourceFileFlags, updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
import { SpecialServers } from "./Server/data/SpecialServers"; import { SpecialServers } from "./Server/data/SpecialServers";
import { deleteStockMarket, initStockMarket, initSymbolToStockMap } from "./StockMarket/StockMarket"; import { deleteStockMarket, initStockMarket, initSymbolToStockMap } from "./StockMarket/StockMarket";
import { Terminal } from "./Terminal"; import { Terminal } from "./Terminal";
@ -56,15 +54,15 @@ export function prestigeAugmentation(): void {
AddToAllServers(homeComp); AddToAllServers(homeComp);
prestigeHomeComputer(Player, homeComp); prestigeHomeComputer(Player, homeComp);
if (augmentationExists(AugmentationNames.Neurolink) && Augmentations[AugmentationNames.Neurolink].owned) { if (augmentationExists(AugmentationNames.Neurolink) && Player.hasAugmentation(AugmentationNames.Neurolink)) {
homeComp.programs.push(Programs.FTPCrackProgram.name); homeComp.programs.push(Programs.FTPCrackProgram.name);
homeComp.programs.push(Programs.RelaySMTPProgram.name); homeComp.programs.push(Programs.RelaySMTPProgram.name);
} }
if (augmentationExists(AugmentationNames.CashRoot) && Augmentations[AugmentationNames.CashRoot].owned) { if (augmentationExists(AugmentationNames.CashRoot) && Player.hasAugmentation(AugmentationNames.CashRoot)) {
Player.setMoney(1e6); Player.setMoney(1e6);
homeComp.programs.push(Programs.BruteSSHProgram.name); homeComp.programs.push(Programs.BruteSSHProgram.name);
} }
if (augmentationExists(AugmentationNames.PCMatrix) && Augmentations[AugmentationNames.PCMatrix].owned) { if (augmentationExists(AugmentationNames.PCMatrix) && Player.hasAugmentation(AugmentationNames.PCMatrix)) {
homeComp.programs.push(Programs.DeepscanV1.name); homeComp.programs.push(Programs.DeepscanV1.name);
homeComp.programs.push(Programs.AutoLink.name); homeComp.programs.push(Programs.AutoLink.name);
} }
@ -105,9 +103,6 @@ export function prestigeAugmentation(): void {
Player.reapplyAllSourceFiles(); Player.reapplyAllSourceFiles();
initCompanies(); initCompanies();
// Messages
initMessages();
// Apply entropy from grafting // Apply entropy from grafting
Player.applyEntropy(Player.entropy); Player.applyEntropy(Player.entropy);
@ -143,7 +138,7 @@ export function prestigeAugmentation(): void {
if (Player.bitNodeN === 8) { if (Player.bitNodeN === 8) {
Player.money = BitNode8StartingMoney; Player.money = BitNode8StartingMoney;
} }
if (Player.bitNodeN === 8 || SourceFileFlags[8] > 0) { if (Player.bitNodeN === 8 || Player.sourceFileLvl(8) > 0) {
Player.hasWseAccount = true; Player.hasWseAccount = true;
Player.hasTixApiAccess = true; Player.hasTixApiAccess = true;
} }
@ -155,7 +150,7 @@ export function prestigeAugmentation(): void {
} }
// Red Pill // Red Pill
if (augmentationExists(AugmentationNames.TheRedPill) && Augmentations[AugmentationNames.TheRedPill].owned) { if (augmentationExists(AugmentationNames.TheRedPill) && Player.hasAugmentation(AugmentationNames.TheRedPill)) {
const WorldDaemon = GetServer(SpecialServers.WorldDaemon); const WorldDaemon = GetServer(SpecialServers.WorldDaemon);
const DaedalusServer = GetServer(SpecialServers.DaedalusServer); const DaedalusServer = GetServer(SpecialServers.DaedalusServer);
if (WorldDaemon && DaedalusServer) { if (WorldDaemon && DaedalusServer) {
@ -164,7 +159,7 @@ export function prestigeAugmentation(): void {
} }
} }
if (augmentationExists(AugmentationNames.StaneksGift1) && Augmentations[AugmentationNames.StaneksGift1].owned) { if (augmentationExists(AugmentationNames.StaneksGift1) && Player.hasAugmentation(AugmentationNames.StaneksGift1)) {
joinFaction(Factions[FactionNames.ChurchOfTheMachineGod]); joinFaction(Factions[FactionNames.ChurchOfTheMachineGod]);
} }
@ -178,7 +173,6 @@ export function prestigeAugmentation(): void {
// Prestige by destroying Bit Node and gaining a Source File // Prestige by destroying Bit Node and gaining a Source File
export function prestigeSourceFile(flume: boolean): void { export function prestigeSourceFile(flume: boolean): void {
initBitNodeMultipliers(Player); initBitNodeMultipliers(Player);
updateSourceFileFlags(Player);
Player.prestigeSourceFile(); Player.prestigeSourceFile();
prestigeWorkerScripts(); // Delete all Worker Scripts objects prestigeWorkerScripts(); // Delete all Worker Scripts objects
@ -202,9 +196,9 @@ export function prestigeSourceFile(flume: boolean): void {
// Re-create foreign servers // Re-create foreign servers
initForeignServers(Player.getHomeComputer()); initForeignServers(Player.getHomeComputer());
if (SourceFileFlags[9] >= 2) { if (Player.sourceFileLvl(9) >= 2) {
homeComp.setMaxRam(128); homeComp.setMaxRam(128);
} else if (SourceFileFlags[1] > 0) { } else if (Player.sourceFileLvl(1) > 0) {
homeComp.setMaxRam(32); homeComp.setMaxRam(32);
} else { } else {
homeComp.setMaxRam(8); homeComp.setMaxRam(8);
@ -238,10 +232,10 @@ export function prestigeSourceFile(flume: boolean): void {
} }
// Give levels of NeuroFluxGoverner for Source-File 12. Must be done here before Augmentations are recalculated // Give levels of NeuroFluxGoverner for Source-File 12. Must be done here before Augmentations are recalculated
if (SourceFileFlags[12] > 0) { if (Player.sourceFileLvl(12) > 0) {
Player.augmentations.push({ Player.augmentations.push({
name: AugmentationNames.NeuroFluxGovernor, name: AugmentationNames.NeuroFluxGovernor,
level: SourceFileFlags[12], level: Player.sourceFileLvl(12),
}); });
} }
@ -251,9 +245,6 @@ export function prestigeSourceFile(flume: boolean): void {
Player.reapplyAllSourceFiles(); Player.reapplyAllSourceFiles();
initCompanies(); initCompanies();
// Messages
initMessages();
if (Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) { if (Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) {
homeComp.programs.push(Programs.Formulas.name); homeComp.programs.push(Programs.Formulas.name);
} }
@ -271,7 +262,7 @@ export function prestigeSourceFile(flume: boolean): void {
if (Player.bitNodeN === 8) { if (Player.bitNodeN === 8) {
Player.money = BitNode8StartingMoney; Player.money = BitNode8StartingMoney;
} }
if (Player.bitNodeN === 8 || SourceFileFlags[8] > 0) { if (Player.bitNodeN === 8 || Player.sourceFileLvl(8) > 0) {
Player.hasWseAccount = true; Player.hasWseAccount = true;
Player.hasTixApiAccess = true; Player.hasTixApiAccess = true;
} }
@ -299,7 +290,7 @@ export function prestigeSourceFile(flume: boolean): void {
Player.bladeburner = null; Player.bladeburner = null;
// Source-File 9 (level 3) effect // Source-File 9 (level 3) effect
if (SourceFileFlags[9] >= 3) { if (Player.sourceFileLvl(9) >= 3) {
const hserver = Player.createHacknetServer(); const hserver = Player.createHacknetServer();
hserver.level = 100; hserver.level = 100;
@ -316,7 +307,7 @@ export function prestigeSourceFile(flume: boolean): void {
staneksGift.prestigeSourceFile(); staneksGift.prestigeSourceFile();
// Gain int exp // Gain int exp
if (SourceFileFlags[5] !== 0 && !flume) Player.gainIntelligenceExp(300); if (Player.sourceFileLvl(5) !== 0 && !flume) Player.gainIntelligenceExp(300);
resetPidCounter(); resetPidCounter();
} }

@ -24,9 +24,4 @@ export class Program {
this.create = create; this.create = create;
this.run = run; this.run = run;
} }
htmlID(): string {
const name = this.name.endsWith(".exe") ? this.name.slice(0, -".exe".length) : this.name;
return "create-program-" + name;
}
} }

@ -5,7 +5,6 @@ import React from "react";
import { Player } from "./Player"; import { Player } from "./Player";
import { prestigeSourceFile } from "./Prestige"; import { prestigeSourceFile } from "./Prestige";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile"; import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { SourceFiles } from "./SourceFile/SourceFiles"; import { SourceFiles } from "./SourceFile/SourceFiles";
import { dialogBoxCreate } from "./ui/React/DialogBox"; import { dialogBoxCreate } from "./ui/React/DialogBox";
@ -69,7 +68,7 @@ function giveSourceFile(bitNodeNumber: number): void {
export function enterBitNode(router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number): void { export function enterBitNode(router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number): void {
if (!flume) { if (!flume) {
giveSourceFile(destroyedBitNode); giveSourceFile(destroyedBitNode);
} else if (SourceFileFlags[5] === 0 && newBitNode !== 5) { } else if (Player.sourceFileLvl(5) === 0 && newBitNode !== 5) {
Player.intelligence = 0; Player.intelligence = 0;
Player.intelligence_exp = 0; Player.intelligence_exp = 0;
} }

@ -3,11 +3,9 @@ import { Companies, loadCompanies } from "./Company/Companies";
import { CONSTANTS } from "./Constants"; import { CONSTANTS } from "./Constants";
import { Factions, loadFactions } from "./Faction/Factions"; import { Factions, loadFactions } from "./Faction/Factions";
import { loadAllGangs, AllGangs } from "./Gang/AllGangs"; import { loadAllGangs, AllGangs } from "./Gang/AllGangs";
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
import { Player, loadPlayer } from "./Player"; import { Player, loadPlayer } from "./Player";
import { saveAllServers, loadAllServers, GetAllServers } from "./Server/AllServers"; import { saveAllServers, loadAllServers, GetAllServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings"; import { Settings } from "./Settings/Settings";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket"; import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
import { staneksGift, loadStaneksGift } from "./CotMG/Helper"; import { staneksGift, loadStaneksGift } from "./CotMG/Helper";
@ -67,7 +65,6 @@ class BitburnerSaveObject {
FactionsSave = ""; FactionsSave = "";
AliasesSave = ""; AliasesSave = "";
GlobalAliasesSave = ""; GlobalAliasesSave = "";
MessagesSave = "";
StockMarketSave = ""; StockMarketSave = "";
SettingsSave = ""; SettingsSave = "";
VersionSave = ""; VersionSave = "";
@ -83,7 +80,6 @@ class BitburnerSaveObject {
this.FactionsSave = JSON.stringify(Factions); this.FactionsSave = JSON.stringify(Factions);
this.AliasesSave = JSON.stringify(Aliases); this.AliasesSave = JSON.stringify(Aliases);
this.GlobalAliasesSave = JSON.stringify(GlobalAliases); this.GlobalAliasesSave = JSON.stringify(GlobalAliases);
this.MessagesSave = JSON.stringify(Messages);
this.StockMarketSave = JSON.stringify(StockMarket); this.StockMarketSave = JSON.stringify(StockMarket);
this.SettingsSave = JSON.stringify(Settings); this.SettingsSave = JSON.stringify(Settings);
this.VersionSave = JSON.stringify(CONSTANTS.VersionNumber); this.VersionSave = JSON.stringify(CONSTANTS.VersionNumber);
@ -129,7 +125,7 @@ class BitburnerSaveObject {
// Save file name is based on current timestamp and BitNode // Save file name is based on current timestamp and BitNode
const epochTime = Math.round(Date.now() / 1000); const epochTime = Math.round(Date.now() / 1000);
const bn = Player.bitNodeN; const bn = Player.bitNodeN;
let filename = `bitburnerSave_${epochTime}_BN${bn}x${SourceFileFlags[bn]}.json`; let filename = `bitburnerSave_${epochTime}_BN${bn}x${Player.sourceFileLvl(bn) + 1}.json`;
if (isRecovery) filename = "RECOVERY" + filename; if (isRecovery) filename = "RECOVERY" + filename;
return filename; return filename;
} }
@ -398,6 +394,9 @@ function evaluateVersionCompatibility(ver: string | number): void {
delete anyPlayer.resleeves; delete anyPlayer.resleeves;
} }
} }
if (ver < 14) {
delete (Settings as any).EditorTheme;
}
} }
} }
@ -441,17 +440,6 @@ function loadGame(saveString: string): boolean {
console.warn(`Save file did not contain a GlobalAliases property`); console.warn(`Save file did not contain a GlobalAliases property`);
loadGlobalAliases(""); loadGlobalAliases("");
} }
if (saveObj.hasOwnProperty("MessagesSave")) {
try {
loadMessages(saveObj.MessagesSave);
} catch (e) {
console.warn(`Could not load Messages from save`);
initMessages();
}
} else {
console.warn(`Save file did not contain a Messages property`);
initMessages();
}
if (saveObj.hasOwnProperty("StockMarketSave")) { if (saveObj.hasOwnProperty("StockMarketSave")) {
try { try {
loadStockMarket(saveObj.StockMarketSave); loadStockMarket(saveObj.StockMarketSave);

@ -2386,6 +2386,30 @@ export interface Singularity {
* purchased. Throws an error if the specified program/exploit does not exist * purchased. Throws an error if the specified program/exploit does not exist
*/ */
getDarkwebProgramCost(programName: string): number; getDarkwebProgramCost(programName: string): number;
/**
* b1t_flum3 into a different BN.
* @remarks
* RAM cost: 16 GB * 16/4/1
*
* @param nextBN - BN number to jump to
* @param callbackScript - Name of the script to launch in the next BN.
*/
b1tflum3(nextBN: number, callbackScript?: string): void;
/**
* Destroy the w0r1d_d43m0n and move on to the next BN.
* @remarks
* RAM cost: 32 GB * 16/4/1
*
* You must have the special augment installed and the required hacking level
* OR
* Completed the final black op.
*
* @param nextBN - BN number to jump to
* @param callbackScript - Name of the script to launch in the next BN.
*/
destroyW0r1dD43m0n(nextBN: number, callbackScript?: string): void;
} }
/** /**
@ -3234,7 +3258,7 @@ export interface CodingContract {
* Attempts to solve the Coding Contract with the provided solution. * Attempts to solve the Coding Contract with the provided solution.
* *
* @param answer - Solution for the contract. * @param answer - Solution for the contract.
* @param fn - Filename of the contract. * @param filename - Filename of the contract.
* @param host - Host of the server containing the contract. Optional. Defaults to current server if not provided. * @param host - Host of the server containing the contract. Optional. Defaults to current server if not provided.
* @param opts - Optional parameters for configuring function behavior. * @param opts - Optional parameters for configuring function behavior.
* @returns True if the solution was correct, false otherwise. If the returnReward option is configured, then the function will instead return a string. If the contract is successfully solved, the string will contain a description of the contracts reward. Otherwise, it will be an empty string. * @returns True if the solution was correct, false otherwise. If the returnReward option is configured, then the function will instead return a string. If the contract is successfully solved, the string will contain a description of the contracts reward. Otherwise, it will be an empty string.
@ -3249,7 +3273,7 @@ export interface CodingContract {
* Returns a name describing the type of problem posed by the Coding Contract. * Returns a name describing the type of problem posed by the Coding Contract.
* (e.g. Find Largest Prime Factor, Total Ways to Sum, etc.) * (e.g. Find Largest Prime Factor, Total Ways to Sum, etc.)
* *
* @param fn - Filename of the contract. * @param filename - Filename of the contract.
* @param host - Host of the server containing the contract. Optional. Defaults to current server if not provided. * @param host - Host of the server containing the contract. Optional. Defaults to current server if not provided.
* @returns Name describing the type of problem posed by the Coding Contract. * @returns Name describing the type of problem posed by the Coding Contract.
*/ */
@ -3262,7 +3286,7 @@ export interface CodingContract {
* *
* Get the full text description for the problem posed by the Coding Contract. * Get the full text description for the problem posed by the Coding Contract.
* *
* @param fn - Filename of the contract. * @param filename - Filename of the contract.
* @param host - Host of the server containing the contract. Optional. Defaults to current server if not provided. * @param host - Host of the server containing the contract. Optional. Defaults to current server if not provided.
* @returns Contracts text description. * @returns Contracts text description.
*/ */
@ -3290,7 +3314,7 @@ export interface CodingContract {
* *
* Get the number of tries remaining on the contract before it self-destructs. * Get the number of tries remaining on the contract before it self-destructs.
* *
* @param fn - Filename of the contract. * @param filename - Filename of the contract.
* @param host - Host of the server containing the contract. Optional. Defaults to current server if not provided. * @param host - Host of the server containing the contract. Optional. Defaults to current server if not provided.
* @returns How many attempts are remaining for the contract; * @returns How many attempts are remaining for the contract;
*/ */
@ -3784,6 +3808,18 @@ export interface Grafting {
*/ */
getAugmentationGraftTime(augName: string): number; getAugmentationGraftTime(augName: string): number;
/**
* Retrieves a list of Augmentations that can be grafted.
* @remarks
* RAM cost: 5 GB
*
* Note that this function returns a list of currently graftable Augmentations,
* based off of the Augmentations that you already own.
*
* @returns An array of graftable Augmentations.
*/
getGraftableAugmentations(): string[];
/** /**
* Begins grafting the named aug. You must be in New Tokyo to use this. * Begins grafting the named aug. You must be in New Tokyo to use this.
* @remarks * @remarks
@ -4693,9 +4729,11 @@ export interface NS {
* Returns the security increase that would occur if a grow with this many threads happened. * Returns the security increase that would occur if a grow with this many threads happened.
* *
* @param threads - Amount of threads that will be used. * @param threads - Amount of threads that will be used.
* @param hostname - Optional. Hostname of the target server. The number of threads is limited to the number needed to hack the servers maximum amount of money.
* @param cores - Optional. The number of cores of the server that would run grow.
* @returns The security increase. * @returns The security increase.
*/ */
growthAnalyzeSecurity(threads: number): number; growthAnalyzeSecurity(threads: number, hostname?: string, cores?: number): number;
/** /**
* Suspends the script for n milliseconds. * Suspends the script for n milliseconds.
@ -5129,8 +5167,7 @@ export interface NS {
* PID stands for Process ID. The PID is a unique identifier for each script. * PID stands for Process ID. The PID is a unique identifier for each script.
* The PID will always be a positive integer. * The PID will always be a positive integer.
* *
* Running this function with a numThreads argument of 0 will return 0 without running the script. * Running this function with 0 or a negative numThreads argument will cause a runtime error.
* However, running this function with a negative numThreads argument will cause a runtime error.
* *
* @example * @example
* ```ts * ```ts
@ -6781,8 +6818,9 @@ export interface WarehouseAPI {
* Upgrade warehouse * Upgrade warehouse
* @param divisionName - Name of the division * @param divisionName - Name of the division
* @param cityName - Name of the city * @param cityName - Name of the city
* @param amt - amount of upgrades defaults to 1
*/ */
upgradeWarehouse(divisionName: string, cityName: string): void; upgradeWarehouse(divisionName: string, cityName: string, amt?: number): void;
/** /**
* Create a new product * Create a new product
* @param divisionName - Name of the division * @param divisionName - Name of the division
@ -6798,6 +6836,22 @@ export interface WarehouseAPI {
designInvest: number, designInvest: number,
marketingInvest: number, marketingInvest: number,
): void; ): void;
/**
* Limit Material Production.
* @param divisionName - Name of the division
* @param cityName - Name of the city
* @param materialName - Name of the material
* @param qty - Amount to limit to
*/
limitMaterialProduction(divisionName: string, cityName: string, materialName: string, qty: number): void;
/**
* Limit Product Production.
* @param divisionName - Name of the division
* @param cityName - Name of the city
* @param productName - Name of the product
* @param qty - Amount to limit to
*/
limitProductProduction(divisionName: string, cityName: string, productName: string, qty: number): void;
/** /**
* Gets the cost to purchase a warehouse * Gets the cost to purchase a warehouse
* @returns cost * @returns cost
@ -6805,9 +6859,12 @@ export interface WarehouseAPI {
getPurchaseWarehouseCost(): number; getPurchaseWarehouseCost(): number;
/** /**
* Gets the cost to upgrade a warehouse to the next level * Gets the cost to upgrade a warehouse to the next level
* @param divisionName - Name of the division
* @param cityName - Name of the city
* @param amt - amount of upgrades defaults to 1
* @returns cost to upgrade * @returns cost to upgrade
*/ */
getUpgradeWarehouseCost(adivisionName: any, acityName: any): number; getUpgradeWarehouseCost(adivisionName: any, acityName: any, amt?: number): number;
/** /**
* Check if you have a warehouse in city * Check if you have a warehouse in city
* @returns true if warehouse is present, false if not * @returns true if warehouse is present, false if not
@ -7051,8 +7108,10 @@ interface Material {
cmp: number | undefined; cmp: number | undefined;
/** Amount of material produced */ /** Amount of material produced */
prod: number; prod: number;
/** Amount of material sold */ /** Amount of material sold */
sell: number; sell: number;
/** cost to buy material */
cost: number;
} }
/** /**

@ -9,6 +9,10 @@ import Select from "@mui/material/Select";
import Switch from "@mui/material/Switch"; import Switch from "@mui/material/Switch";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import EditIcon from "@mui/icons-material/Edit";
import SaveIcon from "@mui/icons-material/Save";
import { ThemeEditorModal } from "./ThemeEditorModal";
interface IProps { interface IProps {
options: Options; options: Options;
@ -23,6 +27,7 @@ export function OptionsModal(props: IProps): React.ReactElement {
const [fontSize, setFontSize] = useState(props.options.fontSize); const [fontSize, setFontSize] = useState(props.options.fontSize);
const [wordWrap, setWordWrap] = useState(props.options.wordWrap); const [wordWrap, setWordWrap] = useState(props.options.wordWrap);
const [vim, setVim] = useState(props.options.vim); const [vim, setVim] = useState(props.options.vim);
const [themeEditorOpen, setThemeEditorOpen] = useState(false);
function save(): void { function save(): void {
props.save({ props.save({
@ -43,6 +48,7 @@ export function OptionsModal(props: IProps): React.ReactElement {
return ( return (
<Modal open={props.open} onClose={props.onClose}> <Modal open={props.open} onClose={props.onClose}>
<ThemeEditorModal open={themeEditorOpen} onClose={() => setThemeEditorOpen(false)} />
<Box display="flex" flexDirection="row" alignItems="center"> <Box display="flex" flexDirection="row" alignItems="center">
<Typography>Theme: </Typography> <Typography>Theme: </Typography>
<Select onChange={(event) => setTheme(event.target.value)} value={theme}> <Select onChange={(event) => setTheme(event.target.value)} value={theme}>
@ -53,7 +59,11 @@ export function OptionsModal(props: IProps): React.ReactElement {
<MenuItem value="light">light</MenuItem> <MenuItem value="light">light</MenuItem>
<MenuItem value="dracula">dracula</MenuItem> <MenuItem value="dracula">dracula</MenuItem>
<MenuItem value="one-dark">one-dark</MenuItem> <MenuItem value="one-dark">one-dark</MenuItem>
<MenuItem value="customTheme">Custom theme</MenuItem>
</Select> </Select>
<Button onClick={() => setThemeEditorOpen(true)} sx={{ mx: 1 }} startIcon={<EditIcon />}>
Edit custom theme
</Button>
</Box> </Box>
<Box display="flex" flexDirection="row" alignItems="center"> <Box display="flex" flexDirection="row" alignItems="center">
@ -80,7 +90,9 @@ export function OptionsModal(props: IProps): React.ReactElement {
<TextField type="number" label="Font size" value={fontSize} onChange={onFontChange} /> <TextField type="number" label="Font size" value={fontSize} onChange={onFontChange} />
</Box> </Box>
<br /> <br />
<Button onClick={save}>Save</Button> <Button onClick={save} startIcon={<SaveIcon />}>
Save
</Button>
</Modal> </Modal>
); );
} }

@ -25,7 +25,7 @@ import { Settings } from "../../Settings/Settings";
import { iTutorialNextStep, ITutorial, iTutorialSteps } from "../../InteractiveTutorial"; import { iTutorialNextStep, ITutorial, iTutorialSteps } from "../../InteractiveTutorial";
import { debounce } from "lodash"; import { debounce } from "lodash";
import { saveObject } from "../../SaveObject"; import { saveObject } from "../../SaveObject";
import { loadThemes } from "./themes"; import { loadThemes, makeTheme, sanitizeTheme } from "./themes";
import { GetServer } from "../../Server/AllServers"; import { GetServer } from "../../Server/AllServers";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@ -362,6 +362,8 @@ export function Root(props: IProps): React.ReactElement {
monaco.languages.typescript.javascriptDefaults.addExtraLib(source, "netscript.d.ts"); monaco.languages.typescript.javascriptDefaults.addExtraLib(source, "netscript.d.ts");
monaco.languages.typescript.typescriptDefaults.addExtraLib(source, "netscript.d.ts"); monaco.languages.typescript.typescriptDefaults.addExtraLib(source, "netscript.d.ts");
loadThemes(monaco); loadThemes(monaco);
sanitizeTheme(Settings.EditorTheme);
monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
} }
// When the editor is mounted // When the editor is mounted
@ -993,7 +995,11 @@ export function Root(props: IProps): React.ReactElement {
</Box> </Box>
<OptionsModal <OptionsModal
open={optionsOpen} open={optionsOpen}
onClose={() => setOptionsOpen(false)} onClose={() => {
sanitizeTheme(Settings.EditorTheme);
monacoRef.current?.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
setOptionsOpen(false);
}}
options={{ options={{
theme: Settings.MonacoTheme, theme: Settings.MonacoTheme,
insertSpaces: Settings.MonacoInsertSpaces, insertSpaces: Settings.MonacoInsertSpaces,
@ -1002,6 +1008,8 @@ export function Root(props: IProps): React.ReactElement {
vim: Settings.MonacoVim, vim: Settings.MonacoVim,
}} }}
save={(options: Options) => { save={(options: Options) => {
sanitizeTheme(Settings.EditorTheme);
monacoRef.current?.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
setOptions(options); setOptions(options);
Settings.MonacoTheme = options.theme; Settings.MonacoTheme = options.theme;
Settings.MonacoInsertSpaces = options.insertSpaces; Settings.MonacoInsertSpaces = options.insertSpaces;

@ -0,0 +1,276 @@
import { History, Reply, Save } from "@mui/icons-material";
import { Box, Button, Paper, TextField, Tooltip, Typography } from "@mui/material";
import IconButton from "@mui/material/IconButton";
import _ from "lodash";
import { Color, ColorPicker } from "material-ui-color";
import React, { useState } from "react";
import { Settings } from "../../Settings/Settings";
import { Modal } from "../../ui/React/Modal";
import { OptionSwitch } from "../../ui/React/OptionSwitch";
import { defaultMonacoTheme, IScriptEditorTheme } from "./themes";
interface IProps {
onClose: () => void;
open: boolean;
}
interface IColorEditorProps {
label: string;
themePath: string;
color: string | undefined;
onColorChange: (name: string, value: string) => void;
defaultColor: string;
}
// Slightly tweaked version of the same function found in game options
function ColorEditor({ label, themePath, onColorChange, color, defaultColor }: IColorEditorProps): React.ReactElement {
if (color === undefined) {
console.error(`color ${themePath} was undefined, reverting to default`);
color = defaultColor;
}
return (
<>
<Tooltip title={label}>
<span>
<TextField
label={themePath}
value={"#" + color}
sx={{ display: "block", my: 1 }}
InputProps={{
startAdornment: (
<>
<ColorPicker
hideTextfield
deferred
value={"#" + color}
onChange={(newColor: Color) => onColorChange(themePath, newColor.hex)}
disableAlpha
/>
</>
),
endAdornment: (
<>
<IconButton onClick={() => onColorChange(themePath, defaultColor)}>
<Reply color="primary" />
</IconButton>
</>
),
}}
/>
</span>
</Tooltip>
</>
);
}
export function ThemeEditorModal(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((o) => !o);
}
// Need to deep copy the object since it has nested attributes
const [themeCopy, setThemeCopy] = useState<IScriptEditorTheme>(JSON.parse(JSON.stringify(Settings.EditorTheme)));
function onColorChange(name: string, value: string): void {
setThemeCopy(_.set(themeCopy, name, value));
rerender();
}
function onThemeChange(event: React.ChangeEvent<HTMLInputElement>): void {
try {
const importedTheme = JSON.parse(event.target.value);
if (typeof importedTheme !== "object") return;
setThemeCopy(importedTheme);
} catch (err) {
// ignore
}
}
return (
<Modal
open={props.open}
onClose={() => {
setThemeCopy(Settings.EditorTheme);
props.onClose();
}}
>
<Typography variant="h4">Customize Editor theme</Typography>
<Typography>Hover over input boxes for more information</Typography>
<Paper sx={{ p: 1, my: 1 }}>
<OptionSwitch
checked={themeCopy.base === "vs"}
onChange={(val) => {
setThemeCopy(_.set(themeCopy, "base", val ? "vs" : "vs-dark"));
rerender();
}}
text="Use light theme as base"
tooltip={
<>
If enabled, the <code>vs</code> light theme will be used as the theme base, otherwise,{" "}
<code>vs-dark</code> will be used.
</>
}
/>
<Box display="grid" sx={{ gridTemplateColumns: "1fr 1fr", width: "fit-content", gap: 1 }}>
<Box>
<Typography variant="h6">UI</Typography>
<ColorEditor
label="Background color"
themePath="common.bg"
onColorChange={onColorChange}
color={themeCopy.common.bg}
defaultColor={defaultMonacoTheme.common.bg}
/>
<ColorEditor
label="Current line and minimap background color"
themePath="ui.line"
onColorChange={onColorChange}
color={themeCopy.ui.line}
defaultColor={defaultMonacoTheme.ui.line}
/>
<ColorEditor
label="Base text color"
themePath="common.fg"
onColorChange={onColorChange}
color={themeCopy.common.fg}
defaultColor={defaultMonacoTheme.common.fg}
/>
<ColorEditor
label="Popup background color"
themePath="ui.panel.bg"
onColorChange={onColorChange}
color={themeCopy.ui.panel.bg}
defaultColor={defaultMonacoTheme.ui.panel.bg}
/>
<ColorEditor
label="Background color for selected item in popup"
themePath="ui.panel.selected"
onColorChange={onColorChange}
color={themeCopy.ui.panel.selected}
defaultColor={defaultMonacoTheme.ui.panel.selected}
/>
<ColorEditor
label="Popup border color"
themePath="ui.panel.border"
onColorChange={onColorChange}
color={themeCopy.ui.panel.border}
defaultColor={defaultMonacoTheme.ui.panel.border}
/>
<ColorEditor
label="Background color of highlighted text"
themePath="ui.selection.bg"
onColorChange={onColorChange}
color={themeCopy.ui.selection.bg}
defaultColor={defaultMonacoTheme.ui.selection.bg}
/>
</Box>
<Box>
<Typography variant="h6">Syntax</Typography>
<ColorEditor
label="Numbers, function names, and other key vars"
themePath="common.accent"
onColorChange={onColorChange}
color={themeCopy.common.accent}
defaultColor={defaultMonacoTheme.common.accent}
/>
<ColorEditor
label="Keywords"
themePath="syntax.keyword"
onColorChange={onColorChange}
color={themeCopy.syntax.keyword}
defaultColor={defaultMonacoTheme.syntax.keyword}
/>
<ColorEditor
label="Strings"
themePath="syntax.string"
onColorChange={onColorChange}
color={themeCopy.syntax.string}
defaultColor={defaultMonacoTheme.syntax.string}
/>
<ColorEditor
label="Regexp literals as well as escapes within strings"
themePath="syntax.regexp"
onColorChange={onColorChange}
color={themeCopy.syntax.regexp}
defaultColor={defaultMonacoTheme.syntax.regexp}
/>
<ColorEditor
label="Constants"
themePath="syntax.constant"
onColorChange={onColorChange}
color={themeCopy.syntax.constant}
defaultColor={defaultMonacoTheme.syntax.constant}
/>
<ColorEditor
label="Entities"
themePath="syntax.entity"
onColorChange={onColorChange}
color={themeCopy.syntax.entity}
defaultColor={defaultMonacoTheme.syntax.entity}
/>
<ColorEditor
label="'this', 'ns', types, and tags"
themePath="syntax.tag"
onColorChange={onColorChange}
color={themeCopy.syntax.tag}
defaultColor={defaultMonacoTheme.syntax.tag}
/>
<ColorEditor
label="Netscript functions and constructors"
themePath="syntax.markup"
onColorChange={onColorChange}
color={themeCopy.syntax.markup}
defaultColor={defaultMonacoTheme.syntax.markup}
/>
<ColorEditor
label="Errors"
themePath="syntax.error"
onColorChange={onColorChange}
color={themeCopy.syntax.error}
defaultColor={defaultMonacoTheme.syntax.error}
/>
<ColorEditor
label="Comments"
themePath="syntax.comment"
onColorChange={onColorChange}
color={themeCopy.syntax.comment}
defaultColor={defaultMonacoTheme.syntax.comment}
/>
</Box>
</Box>
</Paper>
<Paper sx={{ p: 1 }}>
<TextField
multiline
fullWidth
maxRows={10}
label={"import / export theme"}
value={JSON.stringify(themeCopy, undefined, 2)}
onChange={onThemeChange}
/>
<Box sx={{ mt: 1 }}>
<Button
onClick={() => {
Settings.EditorTheme = { ...themeCopy };
props.onClose();
}}
startIcon={<Save />}
>
Save
</Button>
<Button
onClick={() => {
setThemeCopy(defaultMonacoTheme);
rerender();
}}
startIcon={<History />}
>
Reset to default
</Button>
</Box>
</Paper>
</Modal>
);
}

@ -1,3 +1,216 @@
export interface IScriptEditorTheme {
[key: string]: any;
base: string;
inherit: boolean;
common: {
[key: string]: string;
accent: string;
bg: string;
fg: string;
};
syntax: {
[key: string]: string;
tag: string;
entity: string;
string: string;
regexp: string;
markup: string;
keyword: string;
comment: string;
constant: string;
error: string;
};
ui: {
[key: string]: any;
line: string;
panel: {
[key: string]: string;
bg: string;
selected: string;
border: string;
};
selection: {
[key: string]: string;
bg: string;
};
};
}
export const defaultMonacoTheme: IScriptEditorTheme = {
base: "vs-dark",
inherit: true,
common: {
accent: "B5CEA8",
bg: "1E1E1E",
fg: "D4D4D4",
},
syntax: {
tag: "569CD6",
entity: "569CD6",
string: "CE9178",
regexp: "646695",
markup: "569CD6",
keyword: "569CD6",
comment: "6A9955",
constant: "569CD6",
error: "F44747",
},
ui: {
line: "1E1E1E",
panel: {
bg: "252526",
selected: "252526",
border: "1E1E1E",
},
selection: {
bg: "ADD6FF26",
},
},
};
// Regex used for token color validation
// https://github.com/microsoft/vscode/blob/973684056e67153952f495fce93bf50d0ec0b892/src/vs/editor/common/languages/supports/tokenization.ts#L153
const colorRegExp = /^#?([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/;
// Recursively sanitize the theme data to prevent errors
// Invalid data will be replaced with FF0000 (bright red)
export const sanitizeTheme = (theme: IScriptEditorTheme): void => {
for (const [k, v] of Object.entries(theme)) {
switch (k) {
case "base":
if (!["vs-dark", "vs"].includes(theme.base)) theme.base = "vs-dark";
continue;
case "inherit":
if (typeof theme.inherit !== "boolean") theme.inherit = true;
continue;
}
const repairBlock = (block: { [key: string]: any }): void => {
for (const [k, v] of Object.entries(block)) {
if (typeof v === "object") {
repairBlock(v as { [key: string]: string });
} else if (!v.match(colorRegExp)) block[k] = "FF0000";
}
};
repairBlock(v);
}
};
export function makeTheme(theme: IScriptEditorTheme): any {
const themeRules = [
{
token: "",
background: theme.ui.line,
foreground: theme.common.fg,
},
{
token: "identifier",
foreground: theme.common.accent,
},
{
token: "keyword",
foreground: theme.syntax.keyword,
},
{
token: "string",
foreground: theme.syntax.string,
},
{
token: "string.escape",
foreground: theme.syntax.regexp,
},
{
token: "comment",
foreground: theme.syntax.comment,
},
{
token: "constant",
foreground: theme.syntax.constant,
},
{
token: "entity",
foreground: theme.syntax.entity,
},
{
token: "type",
foreground: theme.syntax.tag,
},
{
token: "tag",
foreground: theme.syntax.tag,
},
{
token: "regexp",
foreground: theme.syntax.regexp,
},
{
token: "attribute",
foreground: theme.syntax.tag,
},
{
token: "constructor",
foreground: theme.syntax.markup,
},
{
token: "invalid",
foreground: theme.syntax.error,
},
{
token: "number",
foreground: theme.common.accent,
},
{
token: "delimiter",
foreground: theme.common.fg,
},
// Custom tokens
{
token: "ns",
foreground: theme.syntax.tag,
},
{
token: "netscriptfunction",
foreground: theme.syntax.markup,
},
{
token: "otherkeywords",
foreground: theme.syntax.keyword,
},
{
token: "otherkeyvars",
foreground: theme.common.accent,
},
{
token: "this",
foreground: theme.syntax.tag,
},
];
const themeColors = Object.fromEntries(
[
["editor.background", theme.common.bg],
["editor.foreground", theme.common.fg],
["editor.lineHighlightBackground", theme.ui.line],
["editor.selectionBackground", theme.ui.selection.bg],
["editorSuggestWidget.background", theme.ui.panel.bg],
["editorSuggestWidget.border", theme.ui.panel.border],
["editorSuggestWidget.selectedBackground", theme.ui.panel.selected],
["editorHoverWidget.background", theme.ui.panel.bg],
["editorHoverWidget.border", theme.ui.panel.border],
["editorWidget.background", theme.ui.panel.bg],
["editorWidget.border", theme.ui.panel.border],
["input.background", theme.ui.panel.bg],
["input.border", theme.ui.panel.border],
].map(([k, v]) => [k, "#" + v]),
);
return { base: theme.base, inherit: theme.inherit, rules: themeRules, colors: themeColors };
}
export async function loadThemes(monaco: { editor: any }): Promise<void> { export async function loadThemes(monaco: { editor: any }): Promise<void> {
monaco.editor.defineTheme("monokai", { monaco.editor.defineTheme("monokai", {
base: "vs-dark", base: "vs-dark",
@ -261,6 +474,7 @@ export async function loadThemes(monaco: { editor: any }): Promise<void> {
foreground: "FFB86C", foreground: "FFB86C",
fontStyle: "italic", fontStyle: "italic",
}, },
{ {
token: "netscriptfunction", token: "netscriptfunction",
foreground: "FF79C6", foreground: "FF79C6",

@ -5,6 +5,7 @@ import { defaultStyles } from "../Themes/Styles";
import { WordWrapOptions } from "../ScriptEditor/ui/Options"; import { WordWrapOptions } from "../ScriptEditor/ui/Options";
import { OverviewSettings } from "../ui/React/Overview"; import { OverviewSettings } from "../ui/React/Overview";
import { IStyleSettings } from "../ScriptEditor/NetscriptDefinitions"; import { IStyleSettings } from "../ScriptEditor/NetscriptDefinitions";
import { defaultMonacoTheme, IScriptEditorTheme } from "../ScriptEditor/ui/themes";
/** /**
* Represents the default settings the player could customize. * Represents the default settings the player could customize.
@ -157,6 +158,11 @@ interface IDefaultSettings {
* If the game's sidebar is opened * If the game's sidebar is opened
*/ */
IsSidebarOpened: boolean; IsSidebarOpened: boolean;
/**
* Script editor theme data
*/
EditorTheme: IScriptEditorTheme;
} }
/** /**
@ -216,6 +222,8 @@ export const defaultSettings: IDefaultSettings = {
theme: defaultTheme, theme: defaultTheme,
styles: defaultStyles, styles: defaultStyles,
overview: { x: 0, y: 0, opened: true }, overview: { x: 0, y: 0, opened: true },
EditorTheme: defaultMonacoTheme,
}; };
/** /**
@ -262,6 +270,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
theme: { ...defaultTheme }, theme: { ...defaultTheme },
styles: { ...defaultStyles }, styles: { ...defaultStyles },
overview: defaultSettings.overview, overview: defaultSettings.overview,
EditorTheme: { ...defaultMonacoTheme },
init() { init() {
Object.assign(Settings, defaultSettings); Object.assign(Settings, defaultSettings);
}, },
@ -273,6 +282,8 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
delete save.styles; delete save.styles;
Object.assign(Settings.overview, save.overview); Object.assign(Settings.overview, save.overview);
delete save.overview; delete save.overview;
Object.assign(Settings.EditorTheme, save.EditorTheme);
delete save.EditorTheme;
Object.assign(Settings, save); Object.assign(Settings, save);
}, },
}; };

@ -1,18 +0,0 @@
// Contains an array containing information about the player's source files
// Array[n] returns what level the player has of Source-File N.
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
export const SourceFileFlags: number[] = Array(CONSTANTS.TotalNumBitNodes + 1); // Skip index 0
export function updateSourceFileFlags(p: IPlayer): void {
for (let i = 0; i < SourceFileFlags.length; ++i) {
SourceFileFlags[i] = 0;
}
for (let i = 0; i < p.sourceFiles.length; ++i) {
const sf = p.sourceFiles[i];
SourceFileFlags[sf.n] = sf.lvl;
}
}

@ -16,7 +16,6 @@ import { OrderTypes } from "../data/OrderTypes";
import { PositionTypes } from "../data/PositionTypes"; import { PositionTypes } from "../data/PositionTypes";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
@ -288,12 +287,12 @@ export function StockTicker(props: IProps): React.ReactElement {
// Whether the player has access to orders besides market orders (limit/stop) // Whether the player has access to orders besides market orders (limit/stop)
function hasOrderAccess(): boolean { function hasOrderAccess(): boolean {
return props.p.bitNodeN === 8 || SourceFileFlags[8] >= 3; return props.p.bitNodeN === 8 || props.p.sourceFileLvl(8) >= 3;
} }
// Whether the player has access to shorting stocks // Whether the player has access to shorting stocks
function hasShortAccess(): boolean { function hasShortAccess(): boolean {
return props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2; return props.p.bitNodeN === 8 || props.p.sourceFileLvl(8) >= 2;
} }
return ( return (

@ -9,7 +9,6 @@ import { Stock } from "../Stock";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
@ -67,7 +66,7 @@ function ShortPosition(props: IProps): React.ReactElement {
percentageGains = 0; percentageGains = 0;
} }
if (props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2) { if (props.p.bitNodeN === 8 || props.p.sourceFileLvl(8) >= 2) {
return ( return (
<> <>
<Box display="flex"> <Box display="flex">

@ -51,3 +51,14 @@ export function getSubdirectories(serv: BaseServer, dir: string): string[] {
return res; return res;
} }
/**
* Returns true, if the server's directory itself or one of its subdirectory contains files.
*/
export function containsFiles(server: BaseServer, dir: string): boolean {
const dirWithTrailingSlash = dir + (dir.slice(-1) === "/" ? "" : "/");
return [...server.scripts.map((s) => s.filename), ...server.textFiles.map((t) => t.fn)].some((filename) =>
filename.startsWith(dirWithTrailingSlash),
);
}

@ -2,9 +2,10 @@ import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer"; import { BaseServer } from "../../Server/BaseServer";
import { showMessage } from "../../Message/MessageHelpers"; import { MessageFilenames, showMessage } from "../../Message/MessageHelpers";
import { showLiterature } from "../../Literature/LiteratureHelpers"; import { showLiterature } from "../../Literature/LiteratureHelpers";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { checkEnum } from "../../utils/helpers/checkEnum";
export function cat( export function cat(
terminal: ITerminal, terminal: ITerminal,
@ -43,6 +44,7 @@ export function cat(
} else if (filename.endsWith(".msg")) { } else if (filename.endsWith(".msg")) {
const file = server.messages[i]; const file = server.messages[i];
if (file !== filename) continue; if (file !== filename) continue;
if (!checkEnum(MessageFilenames, file)) return;
showMessage(file); showMessage(file);
return; return;
} }

@ -4,6 +4,7 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer"; import { BaseServer } from "../../Server/BaseServer";
import { evaluateDirectoryPath, removeTrailingSlash } from "../DirectoryHelpers"; import { evaluateDirectoryPath, removeTrailingSlash } from "../DirectoryHelpers";
import { containsFiles } from "../DirectoryServerHelpers";
export function cd( export function cd(
terminal: ITerminal, terminal: ITerminal,
@ -31,10 +32,7 @@ export function cd(
} }
const server = player.getCurrentServer(); const server = player.getCurrentServer();
if ( if (!containsFiles(server, evaledDir)) {
!server.scripts.some((script) => script.filename.startsWith(evaledDir + "")) &&
!server.textFiles.some((file) => file.fn.startsWith(evaledDir + ""))
) {
terminal.error("Invalid path. Failed to change directories"); terminal.error("Invalid path. Failed to change directories");
return; return;
} }

@ -366,9 +366,9 @@ export function ThemeEditorModal(props: IProps): React.ReactElement {
sx={{ mb: 1 }} sx={{ mb: 1 }}
multiline multiline
fullWidth fullWidth
maxRows={3} maxRows={10}
label={"import / export theme"} label={"import / export theme"}
value={JSON.stringify(customTheme)} value={JSON.stringify(customTheme, undefined, 2)}
onChange={onThemeChange} onChange={onThemeChange}
/> />
<> <>

Some files were not shown because too many files have changed in this diff Show More