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
.history
.vscode
Changelog.txt
Netburner.txt

16
dist/bitburner.d.ts vendored

@ -1773,6 +1773,18 @@ export declare interface Grafting {
*/
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.
* @remarks
@ -2911,9 +2923,11 @@ export declare interface NS {
* 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 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.
*/
growthAnalyzeSecurity(threads: number): number;
growthAnalyzeSecurity(threads: number, hostname?: string, cores?: number): number;
/**
* 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.
version = '1.6'
# 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
# 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",
"version": "1.6.3",
"version": "1.6.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "1.6.3",
"version": "1.6.4",
"dependencies": {
"electron-config": "^2.0.0",
"electron-log": "^4.4.4",

@ -1,6 +1,6 @@
{
"name": "bitburner",
"version": "1.6.3",
"version": "1.6.4",
"description": "A cyberpunk-themed programming incremental game",
"main": "main.js",
"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. |
| [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. |

@ -9,7 +9,7 @@ Calculate the security increase for a number of thread.
<b>Signature:</b>
```typescript
growthAnalyzeSecurity(threads: number): number;
growthAnalyzeSecurity(threads: number, hostname?: string, cores?: number): number;
```
## Parameters
@ -17,6 +17,8 @@ growthAnalyzeSecurity(threads: number): number;
| Parameter | Type | Description |
| --- | --- | --- |
| 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>

@ -115,7 +115,7 @@ export async function main(ns) {
| [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. |
| [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. |
| [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. |

4
package-lock.json generated

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

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

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

@ -7,7 +7,6 @@ import { CONSTANTS } from "../Constants";
import { Factions, factionExists } from "../Faction/Factions";
import { Player } from "../Player";
import { prestigeAugmentation } from "../Prestige";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { clearObject } from "../utils/helpers/clearObject";
@ -75,7 +74,7 @@ function initAugmentations(): void {
}
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 {
return Math.pow(getBaseAugmentationPriceMultiplier(), Player.queuedAugmentations.length);
@ -134,8 +133,6 @@ function resetAugmentation(aug: Augmentation): void {
}
function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void {
Augmentations[aug.name].owned = true;
const augObj = Augmentations[aug.name];
// Apply multipliers

@ -7,7 +7,6 @@ import * as React from "react";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentations } from "../Augmentations";
@ -35,7 +34,7 @@ interface IBitNodeModifiedStatsProps {
function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactElement {
// 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 (

@ -1,5 +1,4 @@
import React, { useState } from "react";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { IRouter } from "../../ui/Router";
import { BitNodes } from "../BitNode";
import { enterBitNode } from "../../RedPill";
@ -128,12 +127,6 @@ export function BitverseRoot(props: IProps): React.ReactElement {
const destroyed = player.bitNodeN;
const [destroySequence, setDestroySequence] = useState(!props.quick);
// Update NextSourceFileFlags
const nextSourceFileFlags = SourceFileFlags.slice();
if (!props.flume) {
if (nextSourceFileFlags[destroyed] < 3) ++nextSourceFileFlags[destroyed];
}
if (destroySequence) {
return (
<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) {
return (
<>
@ -176,7 +178,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<BitNodePortal
key={node.number}
n={node.number}
level={nextSourceFileFlags[node.number]}
level={nextSourceFileLvl(node.number)}
enter={enter}
flume={props.flume}
destroyedBitNode={destroyed}
@ -215,6 +217,8 @@ export function BitverseRoot(props: IProps): React.ReactElement {
</>
);
}
const n = nextSourceFileLvl;
return (
// 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 | | \ O| | | |</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'}}> | <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={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={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={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'}}> \| | / <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'}}> \ \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'}}> <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'}}> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </Typography>
<br />

@ -28,7 +28,6 @@ import { Factions, factionExists } from "../Faction/Factions";
import { calculateHospitalizationCost } from "../Hospital/Hospital";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Settings } from "../Settings/Settings";
import { Augmentations } from "../Augmentation/Augmentations";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { getTimestamp } from "../utils/helpers/getTimestamp";
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 (Augmentations[AugmentationNames.BladesSimulacrum].owned === false && player.isWorking) {
if (player.hasAugmentation(AugmentationNames.BladesSimulacrum) === false && player.isWorking) {
if (this.action.type !== ActionTypes["Idle"]) {
let msg = "Your Bladeburner action was cancelled because you started doing something else.";
if (this.automateEnabled) {

@ -116,8 +116,8 @@ export const CONSTANTS: {
TotalNumBitNodes: number;
LatestUpdate: string;
} = {
VersionString: "1.6.3",
VersionNumber: 13,
VersionString: "1.6.4",
VersionNumber: 14,
// Speed (in ms) at which the main loop is updated
_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 {
if (corporation.funds < CorporationConstants.OfficeInitialCost) {
throw new Error("You don't have enough company funds to open a new office!");
} else {
}
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 {
if (corporation.funds < upgrade[1]) {
if (corporation.funds < upgrade.price) {
throw new Error("Insufficient funds");
}
if (corporation.unlockUpgrades[upgrade[0]] === 1) {
throw new Error(`You have already unlocked the ${upgrade[2]} upgrade!`);
if (corporation.unlockUpgrades[upgrade.index] === 1) {
throw new Error(`You have already unlocked the ${upgrade.name} upgrade!`);
}
corporation.unlock(upgrade);
}
export function LevelUpgrade(corporation: ICorporation, upgrade: CorporationUpgrade): void {
const baseCost = upgrade[1];
const priceMult = upgrade[2];
const level = corporation.upgrades[upgrade[0]];
const baseCost = upgrade.basePrice;
const priceMult = upgrade.priceMult;
const level = corporation.upgrades[upgrade.index];
const cost = baseCost * Math.pow(priceMult, level);
if (corporation.funds < cost) {
throw new Error("Insufficient funds");
@ -345,10 +347,17 @@ export function PurchaseWarehouse(corp: ICorporation, division: IIndustry, city:
corp.funds = corp.funds - CorporationConstants.WarehouseInitialCost;
}
export function UpgradeWarehouse(corp: ICorporation, division: IIndustry, warehouse: Warehouse): void {
const sizeUpgradeCost = CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
export function UpgradeWarehouseCost(warehouse: Warehouse, amt: number): number {
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;
++warehouse.level;
warehouse.level += amt;
warehouse.updateSize(corp, division);
corp.funds = corp.funds - sizeUpgradeCost;
}
@ -416,7 +425,7 @@ export function MakeProduct(
}
const product = new Product({
name: productName.replace(/[<>]/g, ""), //Sanitize for HTMl elements
name: productName.replace(/[<>]/g, "").trim(), //Sanitize for HTMl elements
createCity: city,
designCost: designInvest,
advCost: marketingInvest,
@ -488,12 +497,23 @@ export function CancelExportMaterial(divisionName: string, cityName: string, mat
export function LimitProductProduction(product: Product, cityName: string, qty: number): void {
if (qty < 0 || isNaN(qty)) {
product.prdman[cityName][0] = false;
product.prdman[cityName][1] = 0;
} else {
product.prdman[cityName][0] = true;
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 {
material.marketTa1 = on;
}

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

@ -28,6 +28,7 @@ export const CorporationConstants: {
AllMaterials: string[];
FundingRoundShares: number[];
FundingRoundMultiplier: number[];
AvgProfitLength: number;
} = {
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
@ -83,4 +84,6 @@ export const CorporationConstants: {
],
FundingRoundShares: [0.1, 0.35, 0.25, 0.2],
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
// Upgrades for entire corporation, unlocks features, either you have it or you dont
// The data structure is an array with the following format:
// [index in Corporation feature upgrades array, price, name, description]
export const CorporationUnlockUpgrades: IMap<CorporationUnlockUpgrade> = {
export const CorporationUnlockUpgrades: Record<CorporationUnlockUpgradeIndex, CorporationUnlockUpgrade> = {
//Lets you export goods
"0": [
0,
20e9,
"Export",
[CorporationUnlockUpgradeIndex.Export]: {
index: 0,
price: 20e9,
name: "Export",
desc:
"Develop infrastructure to export your materials to your other facilities. " +
"This allows you to move materials around between different divisions and cities.",
],
},
//Lets you buy exactly however many required materials you need for production
"1": [
1,
25e9,
"Smart Supply",
[CorporationUnlockUpgradeIndex.SmartSupply]: {
index: 1,
price: 25e9,
name: "Smart Supply",
desc:
"Use advanced AI to anticipate your supply needs. " +
"This allows you to purchase exactly however many materials you need for production.",
],
},
//Displays each material/product's demand
"2": [
2,
5e9,
"Market Research - Demand",
[CorporationUnlockUpgradeIndex.MarketResearchDemand]: {
index: 2,
price: 5e9,
name: "Market Research - Demand",
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.",
],
},
//Display's each material/product's competition
"3": [
3,
5e9,
"Market Data - Competition",
[CorporationUnlockUpgradeIndex.MarketDataCompetition]: {
index: 3,
price: 5e9,
name: "Market Data - Competition",
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 " +
"every material and product.",
],
"4": [
4,
10e9,
"VeChain",
},
[CorporationUnlockUpgradeIndex.VeChain]: {
index: 4,
price: 10e9,
name: "VeChain",
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 " +
"Corporation.",
],
"5": [
5,
500e12,
"Shady Accounting",
},
[CorporationUnlockUpgradeIndex.ShadyAccounting]: {
index: 5,
price: 500e12,
name: "Shady Accounting",
desc:
"Utilize unscrupulous accounting practices and pay off government officials to save money " +
"on taxes. This reduces the dividend tax rate by 5%.",
],
"6": [
6,
2e15,
"Government Partnership",
},
[CorporationUnlockUpgradeIndex.GovernmentPartnership]: {
index: 6,
price: 2e15,
name: "Government Partnership",
desc:
"Help national governments further their agendas in exchange for lowered taxes. " +
"This reduces the dividend tax rate by 10%",
],
"7": [7, 50e9, "Warehouse API", "Enables the warehouse API."],
"8": [8, 50e9, "Office API", "Enables the office API."],
},
[CorporationUnlockUpgradeIndex.WarehouseAPI]: {
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
// Upgrades for entire corporation, levelable upgrades
// The data structure is an array with the following format
// [index in Corporation upgrades array, base price, price mult, benefit mult (additive), name, desc]
export const CorporationUpgrades: IMap<CorporationUpgrade> = {
export const CorporationUpgrades: Record<CorporationUpgradeIndex, CorporationUpgrade> = {
//Smart factories, increases production
"0": [
0,
2e9,
1.06,
0.03,
"Smart Factories",
[CorporationUpgradeIndex.SmartFactories]: {
index: CorporationUpgradeIndex.SmartFactories,
basePrice: 2e9,
priceMult: 1.06,
benefit: 0.03,
name: "Smart Factories",
desc:
"Advanced AI automatically optimizes the operation and productivity " +
"of factories. Each level of this upgrade increases your global production by 3% (additive).",
],
},
//Smart warehouses, increases storage size
"1": [
1,
2e9,
1.06,
0.1,
"Smart Storage",
[CorporationUpgradeIndex.SmartStorage]: {
index: CorporationUpgradeIndex.SmartStorage,
basePrice: 2e9,
priceMult: 1.06,
benefit: 0.1,
name: "Smart Storage",
desc:
"Advanced AI automatically optimizes your warehouse storage methods. " +
"Each level of this upgrade increases your global warehouse storage size by 10% (additive).",
],
},
//Advertise through dreams, passive popularity/ awareness gain
"2": [
2,
4e9,
1.1,
0.001,
"DreamSense",
[CorporationUpgradeIndex.DreamSense]: {
index: CorporationUpgradeIndex.DreamSense,
basePrice: 4e9,
priceMult: 1.1,
benefit: 0.001,
name: "DreamSense",
desc:
"Use DreamSense LCC Technologies to advertise your corporation " +
"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," +
"and in popularity by 0.001 / market cycle. A market cycle is approximately " +
"15 seconds.",
],
},
//Makes advertising more effective
"3": [
3,
4e9,
1.5,
0.005,
"Wilson Analytics",
[CorporationUpgradeIndex.WilsonAnalytics]: {
index: CorporationUpgradeIndex.WilsonAnalytics,
basePrice: 4e9,
priceMult: 1.5,
benefit: 0.005,
name: "Wilson Analytics",
desc:
"Purchase data and analysis from Wilson, a marketing research " +
"firm. Each level of this upgrades increases the effectiveness of your " +
"advertising by 0.5% (additive).",
],
},
//Augmentation for employees, increases cre
"4": [
4,
1e9,
1.06,
0.1,
"Nuoptimal Nootropic Injector Implants",
[CorporationUpgradeIndex.NuoptimalNootropicInjectorImplants]: {
index: CorporationUpgradeIndex.NuoptimalNootropicInjectorImplants,
basePrice: 1e9,
priceMult: 1.06,
benefit: 0.1,
name: "Nuoptimal Nootropic Injector Implants",
desc:
"Purchase the Nuoptimal Nootropic " +
"Injector augmentation for your employees. Each level of this upgrade " +
"globally increases the creativity of your employees by 10% (additive).",
],
},
//Augmentation for employees, increases cha
"5": [
5,
1e9,
1.06,
0.1,
"Speech Processor Implants",
[CorporationUpgradeIndex.SpeechProcessorImplants]: {
index: CorporationUpgradeIndex.SpeechProcessorImplants,
basePrice: 1e9,
priceMult: 1.06,
benefit: 0.1,
name: "Speech Processor Implants",
desc:
"Purchase the Speech Processor augmentation for your employees. " +
"Each level of this upgrade globally increases the charisma of your employees by 10% (additive).",
],
},
//Augmentation for employees, increases int
"6": [
6,
1e9,
1.06,
0.1,
"Neural Accelerators",
[CorporationUpgradeIndex.NeuralAccelerators]: {
index: CorporationUpgradeIndex.NeuralAccelerators,
basePrice: 1e9,
priceMult: 1.06,
benefit: 0.1,
name: "Neural Accelerators",
desc:
"Purchase the Neural Accelerator augmentation for your employees. " +
"Each level of this upgrade globally increases the intelligence of your employees " +
"by 10% (additive).",
],
},
//Augmentation for employees, increases eff
"7": [
7,
1e9,
1.06,
0.1,
"FocusWires",
[CorporationUpgradeIndex.FocusWires]: {
index: CorporationUpgradeIndex.FocusWires,
basePrice: 1e9,
priceMult: 1.06,
benefit: 0.1,
name: "FocusWires",
desc:
"Purchase the FocusWire augmentation for your employees. Each level " +
"of this upgrade globally increases the efficiency of your employees by 10% (additive).",
],
},
//Improves sales of materials/products
"8": [
8,
1e9,
1.07,
0.01,
"ABC SalesBots",
[CorporationUpgradeIndex.ABCSalesBots]: {
index: CorporationUpgradeIndex.ABCSalesBots,
basePrice: 1e9,
priceMult: 1.07,
benefit: 0.01,
name: "ABC SalesBots",
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 " +
"by 1% (additive).",
],
},
//Improves scientific research rate
"9": [
9,
5e9,
1.07,
0.05,
"Project Insight",
[CorporationUpgradeIndex.ProjectInsight]: {
index: CorporationUpgradeIndex.ProjectInsight,
basePrice: 5e9,
priceMult: 1.07,
benefit: 0.05,
name: "Project Insight",
desc:
"Purchase 'Project Insight', a R&D service provided by the secretive " +
"Fulcrum Technologies. Each level of this upgrade globally increases the amount of " +
"Scientific Research you produce by 5% (additive).",
],
},
};

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

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

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

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

@ -5,10 +5,10 @@ import React, { useState } from "react";
import { OfficeSpace } from "../OfficeSpace";
import { Material } from "../Material";
import { Warehouse } from "../Warehouse";
import { ExportModal } from "./ExportModal";
import { MaterialMarketTaModal } from "./MaterialMarketTaModal";
import { SellMaterialModal } from "./SellMaterialModal";
import { PurchaseMaterialModal } from "./PurchaseMaterialModal";
import { ExportModal } from "./modals/ExportModal";
import { MaterialMarketTaModal } from "./modals/MaterialMarketTaModal";
import { SellMaterialModal } from "./modals/SellMaterialModal";
import { PurchaseMaterialModal } from "./modals/PurchaseMaterialModal";
import { numeralWrapper } from "../../ui/numeralFormat";
@ -21,6 +21,7 @@ import Tooltip from "@mui/material/Tooltip";
import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
import { LimitMaterialProductionModal } from "./modals/LimitMaterialProductionModal";
interface IMaterialProps {
warehouse: Warehouse;
@ -37,6 +38,8 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
const [exportOpen, setExportOpen] = useState(false);
const [sellMaterialOpen, setSellMaterialOpen] = useState(false);
const [materialMarketTaOpen, setMaterialMarketTaOpen] = useState(false);
const [limitProductionOpen, setLimitProductionOpen] = useState(false);
const warehouse = props.warehouse;
const city = props.city;
const mat = props.mat;
@ -110,6 +113,12 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
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 (
<Paper>
<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>
</Paper>

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

@ -2,10 +2,11 @@ import React, { useState } from "react";
import { CorporationConstants } from "../data/Constants";
import { Product } from "../Product";
import { DiscontinueProductModal } from "./DiscontinueProductModal";
import { LimitProductProductionModal } from "./LimitProductProductionModal";
import { SellProductModal } from "./SellProductModal";
import { ProductMarketTaModal } from "./ProductMarketTaModal";
import { DiscontinueProductModal } from "./modals/DiscontinueProductModal";
import { LimitProductProductionModal } from "./modals/LimitProductProductionModal";
import { SellProductModal } from "./modals/SellProductModal";
import { ProductMarketTaModal } from "./modals/ProductMarketTaModal";
import { CancelProductModal } from "./modals/CancelProductModal";
import { numeralWrapper } from "../../ui/numeralFormat";
@ -32,6 +33,7 @@ export function ProductElem(props: IProductProps): React.ReactElement {
const [sellOpen, setSellOpen] = useState(false);
const [limitOpen, setLimitOpen] = useState(false);
const [discontinueOpen, setDiscontinueOpen] = useState(false);
const [cancelOpen, setCancelOpen] = useState(false);
const [marketTaOpen, setMarketTaOpen] = useState(false);
const city = props.city;
const product = props.product;
@ -111,6 +113,13 @@ export function ProductElem(props: IProductProps): React.ReactElement {
</Typography>
<br />
<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>
</Tooltip>
</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}
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") && (
<>
<Button onClick={() => setMarketTaOpen(true)}>Market-TA</Button>

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

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

@ -1,14 +1,14 @@
import React, { useState } from "react";
import { Modal } from "../../ui/React/Modal";
import { numeralWrapper } from "../../ui/numeralFormat";
import { use } from "../../ui/Context";
import { useCorporation } from "./Context";
import { Modal } from "../../../ui/React/Modal";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { use } from "../../../ui/Context";
import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import { BuyBackShares } from "../Actions";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { KEY } from "../../utils/helpers/keyCodes";
import { BuyBackShares } from "../../Actions";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { KEY } from "../../../utils/helpers/keyCodes";
interface IProps {
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 { Money } from "../../ui/React/Money";
import { Modal } from "../../ui/React/Modal";
import { use } from "../../ui/Context";
import { Money } from "../../../ui/React/Money";
import { Modal } from "../../../ui/React/Modal";
import { use } from "../../../ui/Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";

@ -1,8 +1,8 @@
import React from "react";
import { Product } from "../Product";
import { Modal } from "../../ui/React/Modal";
import { useDivision } from "./Context";
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";

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

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

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

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

@ -1,14 +1,14 @@
import React, { useState } from "react";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { CorporationConstants } from "../data/Constants";
import { useCorporation } from "./Context";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../../ui/React/Modal";
import { getRandomInt } from "../../../utils/helpers/getRandomInt";
import { CorporationConstants } from "../../data/Constants";
import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import { KEY } from "../../utils/helpers/keyCodes";
import { KEY } from "../../../utils/helpers/keyCodes";
interface IEffectTextProps {
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 { Product } from "../Product";
import { LimitProductProduction } from "../Actions";
import { Modal } from "../../ui/React/Modal";
import React, { useEffect, useState } from "react";
import { Product } from "../../Product";
import { LimitProductProduction } 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 { KEY } from "../../../utils/helpers/keyCodes";
interface IProps {
open: boolean;
@ -18,6 +18,13 @@ interface IProps {
export function LimitProductProductionModal(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 limitProductProduction(): void {
let qty = limit;
if (qty === null) qty = -1;

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

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

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

@ -1,16 +1,16 @@
import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { MaterialSizes } from "../MaterialSizes";
import { Warehouse } from "../Warehouse";
import { Material } from "../Material";
import { numeralWrapper } from "../../ui/numeralFormat";
import { BulkPurchase, BuyMaterial } from "../Actions";
import { Modal } from "../../ui/React/Modal";
import { useCorporation, useDivision } from "./Context";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { MaterialSizes } from "../../MaterialSizes";
import { Warehouse } from "../../Warehouse";
import { Material } from "../../Material";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { BulkPurchase, BuyMaterial } from "../../Actions";
import { Modal } from "../../../ui/React/Modal";
import { useCorporation, useDivision } from "../Context";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import { KEY } from "../../utils/helpers/keyCodes";
import { KEY } from "../../../utils/helpers/keyCodes";
interface IBulkPurchaseTextProps {
warehouse: Warehouse;
@ -18,37 +18,6 @@ interface IBulkPurchaseTextProps {
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 {
onClose: () => void;
mat: Material;
@ -58,6 +27,41 @@ interface IBPProps {
function BulkPurchaseSection(props: IBPProps): React.ReactElement {
const corp = useCorporation();
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 {
try {
@ -90,7 +94,9 @@ function BulkPurchaseSection(props: IBPProps): React.ReactElement {
placeholder="Bulk Purchase amount"
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 { Modal } from "../../ui/React/Modal";
import { IndustryResearchTrees } from "../IndustryData";
import { CorporationConstants } from "../data/Constants";
import { IIndustry } from "../IIndustry";
import { Research } from "../Actions";
import { Node } from "../ResearchTree";
import { ResearchMap } from "../ResearchMap";
import { Settings } from "../../Settings/Settings";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { Modal } from "../../../ui/React/Modal";
import { IndustryResearchTrees } from "../../IndustryData";
import { CorporationConstants } from "../../data/Constants";
import { IIndustry } from "../../IIndustry";
import { Research } from "../../Actions";
import { Node } from "../../ResearchTree";
import { ResearchMap } from "../../ResearchMap";
import { Settings } from "../../../Settings/Settings";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Button from "@mui/material/Button";

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

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

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

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

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

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

@ -17,7 +17,7 @@ export function DummyGrid(props: IProps): React.ReactElement {
const ghostGrid = zeros([props.width, props.height]);
return (
<Box>
<Table>
<Table sx={{ width: props.width, height: props.height }}>
<Grid
width={props.width}
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
charged.
</Typography>
<br />
<DummyGrid
width={4}
@ -86,11 +87,12 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
]}
/>
<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>
<br />
<DummyGrid
width={4}
width={3}
height={4}
fragments={[
new ActiveFragment({
@ -108,8 +110,10 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
]}
/>
<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>
<br />
<DummyGrid
width={4}
@ -122,16 +126,17 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
fragment: Fragments.find((f) => f.id === 5) ?? Fragments[0],
}),
new ActiveFragment({
x: 2,
y: 0,
x: 1,
y: 1,
rotation: 0,
fragment: Fragments.find((f) => f.id === 105) ?? Fragments[0],
}),
]}
/>
<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>
<br />
<DummyGrid
width={4}
@ -158,10 +163,10 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
]}
/>
<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>
<br />
<Typography>
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

@ -10,6 +10,11 @@ import Button from "@mui/material/Button";
import { Money } from "../../ui/React/Money";
import { IPlayer } from "../../PersonObjects/IPlayer";
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 {
player: IPlayer;
@ -18,6 +23,8 @@ interface IProps {
export function General(props: IProps): React.ReactElement {
const [error, setError] = useState(false);
const [corporationName, setCorporationName] = useState("");
const [gangFaction, setGangFaction] = useState("");
function addMoney(n: number) {
return function () {
@ -45,6 +52,27 @@ export function General(props: IProps): React.ReactElement {
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(() => {
if (error) throw new ReferenceError("Manually thrown error");
}, [error]);
@ -82,12 +110,29 @@ export function General(props: IProps): React.ReactElement {
</Button>
<Button onClick={upgradeRam}>+ RAM</Button>
<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={b1tflum3}>Run b1t_flum3.exe</Button>
<Button onClick={quickHackW0r1dD43m0n}>Quick w0rld_d34m0n</Button>
<Button onClick={hackW0r1dD43m0n}>Hack w0rld_d34m0n</Button>
<Button onClick={() => setError(true)}>Throw Error</Button>
<Button onClick={checkMessages}>Check Messages</Button>
</AccordionDetails>
</Accordion>
);

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

@ -1,6 +1,8 @@
import React from "react";
import { IMap } from "../types";
import { FactionNames } from "./data/FactionNames";
import { use } from "../ui/Context";
import { Option } from "./ui/Option";
interface FactionInfoParams {
infoText?: JSX.Element;
@ -10,6 +12,7 @@ interface FactionInfoParams {
offerSecurityWork?: boolean;
special?: boolean;
keepOnInstall?: boolean;
assignment?: () => React.ReactElement;
}
/**
@ -51,6 +54,11 @@ export class FactionInfo {
*/
special: boolean;
/**
* The data to display on the faction screen.
*/
assignment?: () => React.ReactElement;
constructor(params: FactionInfoParams) {
this.infoText = params.infoText ?? <></>;
this.enemies = params.enemies ?? [];
@ -60,6 +68,7 @@ export class FactionInfo {
this.keep = params.keepOnInstall ?? false;
this.special = params.special ?? false;
this.assignment = params.assignment;
}
offersWork(): boolean {
@ -438,10 +447,20 @@ export const FactionInfos: IMap<FactionInfo> = {
),
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({
// prettier-ignore
infoText:(<>
{" `` "}<br />
{" -odmmNmds: "}<br />
@ -472,11 +491,25 @@ export const FactionInfos: IMap<FactionInfo> = {
{" -smNNNNmdo- "}<br />
{" `..` "}<br /><br />
Many cultures predict an end to humanity in the near future, a final
Armageddon that will end the world; but we disagree.
<br /><br />Note that for this faction, reputation can
only be gained by charging Stanek's gift.</>),
Armageddon that will end the world; but we disagree.</>),
offerHackingWork: false,
offerFieldWork: false,
offerSecurityWork: false,
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
[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 {
const setRerender = useState(false)[1];
function rerender(): void {
@ -44,6 +55,8 @@ export function Info(props: IProps): React.ReactElement {
const classes = useStyles();
const Assignment = props.factionInfo.assignment ?? DefaultAssignment;
const favorGain = props.faction.getFavorGain();
const offersWork =
props.factionInfo.offerFieldWork || props.factionInfo.offerSecurityWork || props.factionInfo.offerHackingWork;
@ -97,18 +110,7 @@ export function Info(props: IProps): React.ReactElement {
</Box>
<Typography>-------------------------</Typography>
<Typography>
{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>
<Assignment />
</>
);
}

@ -252,6 +252,7 @@ export class Gang implements IGang {
const total = Object.values(AllGangs)
.map((g) => g.territory)
.reduce((p, c) => p + c, 0);
console.log(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 { GetServer } from "../Server/AllServers";
import { Server } from "../Server/Server";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
// Returns a boolean indicating whether the player has Hacknet Servers
// (the upgraded form of Hacknet Nodes)
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 {

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

@ -1,31 +1,14 @@
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver";
import { MessageFilenames } from "./MessageHelpers";
export class Message {
// Name of Message file
filename = "";
filename: MessageFilenames;
// The text contains in the Message
msg = "";
msg: string;
// Flag indicating whether this Message has been received by the player
recvd = false;
constructor(filename = "", msg = "") {
constructor(filename: MessageFilenames, msg: string) {
this.filename = filename;
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 { Page } from "../ui/Router";
import { GetServer } from "../Server/AllServers";
import { SpecialServers } from "../Server/data/SpecialServers";
import { Settings } from "../Settings/Settings";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver } from "../utils/JSONReviver";
import { FactionNames } from "../Faction/data/FactionNames";
import { Server } from "../Server/Server";
//Sends message to player, including a pop up
function sendMessage(msg: Message, forced = false): void {
msg.recvd = true;
if (forced || !Settings.SuppressMessages) {
showMessage(msg.filename);
}
addMessageToServer(msg, "home");
addMessageToServer(msg);
}
function showMessage(name: string): void {
function showMessage(name: MessageFilenames): void {
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 =
"Message received from unknown sender: <br><br>" +
"<i>" +
@ -34,21 +34,27 @@ function showMessage(name: string): void {
}
//Adds a message to a server
function addMessageToServer(msg: Message, serverHostname: string): void {
const server = GetServer(serverHostname);
if (server == null) {
console.warn(`Could not find server ${serverHostname}`);
function addMessageToServer(msg: Message): void {
//Short-circuit if the message has already been saved
if (recvd(msg)) {
return;
}
for (let i = 0; i < server.messages.length; ++i) {
const other = server.messages[i];
if (msg.filename === other) {
return; //Already exists
}
const server = GetServer("home");
if (server == null) {
throw new Error("The home server doesn't exist. You done goofed.");
}
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
function checkForMessagesToSend(): void {
if (Router.page() === Page.BitVerse) return;
@ -60,46 +66,48 @@ function checkForMessagesToSend(): void {
const cybersecTest = Messages[MessageFilenames.CyberSecTest];
const nitesecTest = Messages[MessageFilenames.NiteSecTest];
const bitrunnersTest = Messages[MessageFilenames.BitRunnersTest];
const truthGazer = Messages[MessageFilenames.TruthGazer];
const redpill = Messages[MessageFilenames.RedPill];
if (Player.hasAugmentation(AugmentationNames.TheRedPill)) {
//Force the message if the player has never destroyed a BitNode
//Get the world daemon required hacking level
const worldDaemon = GetServer(SpecialServers.WorldDaemon);
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);
} else if (!jumper0.recvd && Player.hacking >= 25) {
}
//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);
const flightName = Programs.Flight.name;
const homeComp = Player.getHomeComputer();
if (!homeComp.programs.includes(flightName)) {
homeComp.programs.push(flightName);
}
} else if (!jumper1.recvd && Player.hacking >= 40) {
} else if (!recvd(jumper1) && Player.hacking >= 40) {
sendMessage(jumper1);
} else if (!cybersecTest.recvd && Player.hacking >= 50) {
} else if (!recvd(cybersecTest) && Player.hacking >= 50) {
sendMessage(cybersecTest);
} else if (!jumper2.recvd && Player.hacking >= 175) {
} else if (!recvd(jumper2) && Player.hacking >= 175) {
sendMessage(jumper2);
} else if (!nitesecTest.recvd && Player.hacking >= 200) {
} else if (!recvd(nitesecTest) && Player.hacking >= 200) {
sendMessage(nitesecTest);
} else if (!jumper3.recvd && Player.hacking >= 350) {
} else if (!recvd(jumper3) && Player.hacking >= 350) {
sendMessage(jumper3);
} else if (!jumper4.recvd && Player.hacking >= 490) {
} else if (!recvd(jumper4) && Player.hacking >= 490) {
sendMessage(jumper4);
} else if (!bitrunnersTest.recvd && Player.hacking >= 500) {
} else if (!recvd(bitrunnersTest) && Player.hacking >= 500) {
sendMessage(bitrunnersTest);
}
}
function AddToAllMessages(msg: Message): void {
Messages[msg.filename] = msg;
}
let Messages: { [key: string]: Message } = {};
function loadMessages(saveString: string): void {
Messages = JSON.parse(saveString, Reviver);
}
enum MessageFilenames {
export enum MessageFilenames {
Jumper0 = "j0.msg",
Jumper1 = "j1.msg",
Jumper2 = "j2.msg",
@ -108,16 +116,14 @@ enum MessageFilenames {
CyberSecTest = "csec-test.msg",
NiteSecTest = "nitesec-test.msg",
BitRunnersTest = "19dfj3l1nd.msg",
TruthGazer = "truthgazer.msg",
RedPill = "icarus.msg",
}
function initMessages(): void {
//Reset
Messages = {};
//Reset
const Messages: Record<MessageFilenames, Message> = {
//jump3R Messages
AddToAllMessages(
new Message(
[MessageFilenames.Jumper0]: new Message(
MessageFilenames.Jumper0,
"I know you can sense it. I know you're searching for it. " +
"It's why you spend night after " +
@ -127,9 +133,8 @@ function initMessages(): void {
"The fl1ght.exe program was added to your home computer<br><br>" +
"-jump3R",
),
);
AddToAllMessages(
new Message(
[MessageFilenames.Jumper1]: new Message(
MessageFilenames.Jumper1,
`Soon you will be contacted by a hacking group known as ${FactionNames.NiteSec}. ` +
"They can help you with your search. <br><br>" +
@ -138,36 +143,31 @@ function initMessages(): void {
"They are not what they seem. No one is.<br><br>" +
"-jump3R",
),
);
AddToAllMessages(
new Message(
[MessageFilenames.Jumper2]: new Message(
MessageFilenames.Jumper2,
"Do not try to save the world. There is no world to save. If " +
"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",
),
);
AddToAllMessages(
new Message(
[MessageFilenames.Jumper3]: new Message(
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>` +
"I.I.I.I <br><br>-jump3R",
),
);
AddToAllMessages(
new Message(
[MessageFilenames.Jumper4]: 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
AddToAllMessages(
new Message(
[MessageFilenames.CyberSecTest]: new Message(
MessageFilenames.CyberSecTest,
"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 " +
@ -175,9 +175,8 @@ function initMessages(): void {
"But first, you must pass our test. Find and install the backdoor on our server. <br><br>" +
`-${FactionNames.CyberSec}`,
),
);
AddToAllMessages(
new Message(
[MessageFilenames.NiteSecTest]: 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 " +
@ -187,26 +186,33 @@ function initMessages(): void {
"Find and install the backdoor on our server. Then, we will contact you again." +
`<br><br>-${FactionNames.NiteSec}`,
),
);
AddToAllMessages(
new Message(
[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",
),
);
AddToAllMessages(
new Message(
//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, loadMessages, initMessages };
export { Messages, checkForMessagesToSend, showMessage };

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

@ -34,12 +34,12 @@ import { RunningScript } from "./Script/RunningScript";
import {
getServerOnNetwork,
numCycleForGrowth,
numCycleForGrowthCorrected,
processSingleServerGrowth,
safetlyCreateUniqueServer,
} from "./Server/ServerHelpers";
import { getPurchaseServerCost, getPurchaseServerLimit, getPurchaseServerMaxRam } from "./Server/ServerPurchases";
import { Server } from "./Server/Server";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { influenceStockThroughServerHack, influenceStockThroughServerGrow } from "./StockMarket/PlayerInfluencing";
import { isValidFilePath, removeLeadingSlash } from "./Terminal/DirectoryHelpers";
@ -638,7 +638,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
if (percentHacked > 0) {
// 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);
},
growthAnalyzeSecurity: function (_threads: unknown): number {
growthAnalyzeSecurity: function (_threads: unknown, _hostname?: unknown, _cores?: unknown): number {
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;
},
weaken: async function (_hostname: unknown, { threads: requestedThreads }: BasicHGWOptions = {}): Promise<number> {
@ -1535,7 +1552,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
},
getBitNodeMultipliers: function (): IBNMults {
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.");
}
const copy = Object.assign({}, BitNodeMultipliers);

@ -53,6 +53,9 @@ import {
SellShares,
BuyBackShares,
SetSmartSupplyUseLeftovers,
LimitMaterialProduction,
LimitProductProduction,
UpgradeWarehouseCost,
} from "../Corporation/Actions";
import { CorporationUnlockUpgrades } from "../Corporation/data/CorporationUnlockUpgrades";
import { CorporationUpgrades } from "../Corporation/data/CorporationUpgrades";
@ -64,6 +67,7 @@ import { CorporationConstants } from "../Corporation/data/Constants";
import { IndustryUpgrades } from "../Corporation/IndustryUpgrades";
import { ResearchMap } from "../Corporation/ResearchMap";
import { Factions } from "../Faction/Factions";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
export function NetscriptCorporation(
player: IPlayer,
@ -74,6 +78,8 @@ export function NetscriptCorporation(
if (!player.canAccessCorporation() || player.hasCorporation()) return false;
if (!corporationName) return false;
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 (!player.canAfford(150e9)) return false;
@ -88,35 +94,35 @@ export function NetscriptCorporation(
function hasUnlockUpgrade(upgradeName: string): boolean {
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}'`);
const upgN = upgrade[0];
const upgN = upgrade.index;
return corporation.unlockUpgrades[upgN] === 1;
}
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}'`);
return upgrade[1];
return upgrade.price;
}
function getUpgradeLevel(_upgradeName: string): number {
const upgradeName = helper.string("levelUpgrade", "upgradeName", _upgradeName);
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}'`);
const upgN = upgrade[0];
const upgN = upgrade.index;
return corporation.upgrades[upgN];
}
function getUpgradeLevelCost(_upgradeName: string): number {
const upgradeName = helper.string("levelUpgrade", "upgradeName", _upgradeName);
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}'`);
const upgN = upgrade[0];
const baseCost = upgrade[1];
const priceMult = upgrade[2];
const upgN = upgrade.index;
const baseCost = upgrade.basePrice;
const priceMult = upgrade.priceMult;
const level = corporation.upgrades[upgN];
return baseCost * Math.pow(priceMult, level);
}
@ -311,12 +317,16 @@ export function NetscriptCorporation(
checkAccess("getPurchaseWarehouseCost", 7);
return CorporationConstants.WarehouseInitialCost;
},
getUpgradeWarehouseCost: function (_divisionName: unknown, _cityName: unknown): number {
getUpgradeWarehouseCost: function (_divisionName: unknown, _cityName: unknown, _amt: unknown = 1): number {
checkAccess("upgradeWarehouse", 7);
const divisionName = helper.string("getUpgradeWarehouseCost", "divisionName", _divisionName);
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);
return CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
return UpgradeWarehouseCost(warehouse, amt);
},
hasWarehouse: function (_divisionName: unknown, _cityName: unknown): boolean {
checkAccess("hasWarehouse", 7);
@ -348,6 +358,7 @@ export function NetscriptCorporation(
const material = getMaterial(divisionName, cityName, materialName);
const corporation = getCorporation();
return {
cost: material.bCost,
name: material.name,
qty: material.qty,
qlt: material.qlt,
@ -389,12 +400,16 @@ export function NetscriptCorporation(
const corporation = getCorporation();
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);
const divisionName = helper.string("upgradeWarehouse", "divisionName", _divisionName);
const cityName = helper.city("upgradeWarehouse", "cityName", _cityName);
const amt = helper.number("upgradeWarehouse", "amount", _amt);
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 (
_divisionName: unknown,
@ -508,6 +523,19 @@ export function NetscriptCorporation(
const corporation = getCorporation();
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 (
_sourceDivision: unknown,
_sourceCity: unknown,
@ -548,6 +576,19 @@ export function NetscriptCorporation(
const amt = helper.string("cancelExportMaterial", "amt", _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 (
_divisionName: unknown,
_cityName: unknown,
@ -820,7 +861,7 @@ export function NetscriptCorporation(
checkAccess("unlockUpgrade");
const upgradeName = helper.string("unlockUpgrade", "upgradeName", _upgradeName);
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}'`);
UnlockUpgrade(corporation, upgrade);
},
@ -828,7 +869,7 @@ export function NetscriptCorporation(
checkAccess("levelUpgrade");
const upgradeName = helper.string("levelUpgrade", "upgradeName", _upgradeName);
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}'`);
LevelUpgrade(corporation, upgrade);
},

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

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

@ -47,6 +47,8 @@ import { Server } from "../Server/Server";
import { netscriptCanHack } from "../Hacking/netscriptCanHack";
import { FactionInfos } from "../Faction/FactionInfo";
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> {
const getAugmentation = function (_ctx: NetscriptContext, name: string): Augmentation {
@ -94,8 +96,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return {
getOwnedAugmentations: (_ctx: NetscriptContext) =>
function (_purchased: unknown = false): string[] {
const purchased = _ctx.helper.boolean(_purchased);
_ctx.helper.checkSingularityAccess();
const purchased = _ctx.helper.boolean(_purchased);
const res = [];
for (let i = 0; i < player.augmentations.length; ++i) {
res.push(player.augmentations[i].name);
@ -109,52 +111,52 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
getAugmentationsFromFaction: (_ctx: NetscriptContext) =>
function (_facName: unknown): string[] {
const facName = _ctx.helper.string("facName", _facName);
_ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
const faction = getFaction(_ctx, facName);
return getFactionAugmentationsFiltered(player, faction);
},
getAugmentationCost: (_ctx: NetscriptContext) =>
function (_augName: unknown): [number, number] {
const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName);
return [aug.baseRepRequirement, aug.baseCost];
},
getAugmentationPrereq: (_ctx: NetscriptContext) =>
function (_augName: unknown): string[] {
const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName);
return aug.prereqs.slice();
},
getAugmentationPrice: (_ctx: NetscriptContext) =>
function (_augName: unknown): number {
const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName);
return aug.baseCost;
},
getAugmentationRepReq: (_ctx: NetscriptContext) =>
function (_augName: unknown): number {
const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName);
return aug.baseRepRequirement;
},
getAugmentationStats: (_ctx: NetscriptContext) =>
function (_augName: unknown): AugmentationStats {
const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName);
return Object.assign({}, aug.mults);
},
purchaseAugmentation: (_ctx: NetscriptContext) =>
function (_facName: unknown, _augName: unknown): boolean {
_ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
const augName = _ctx.helper.string("augName", _augName);
_ctx.helper.checkSingularityAccess();
const fac = getFaction(_ctx, facName);
const aug = getAugmentation(_ctx, augName);
@ -200,8 +202,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
softReset: (_ctx: NetscriptContext) =>
function (_cbScript: unknown = ""): void {
const cbScript = _ctx.helper.string("cbScript", _cbScript);
_ctx.helper.checkSingularityAccess();
const cbScript = _ctx.helper.string("cbScript", _cbScript);
workerScript.log("softReset", () => "Soft resetting. This will cause this script to be killed");
setTimeout(() => {
@ -215,8 +217,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
installAugmentations: (_ctx: NetscriptContext) =>
function (_cbScript: unknown = ""): boolean {
const cbScript = _ctx.helper.string("cbScript", _cbScript);
_ctx.helper.checkSingularityAccess();
const cbScript = _ctx.helper.string("cbScript", _cbScript);
if (player.queuedAugmentations.length === 0) {
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) =>
function (_locationName: unknown): boolean {
const locationName = _ctx.helper.string("locationName", _locationName);
_ctx.helper.checkSingularityAccess();
const locationName = _ctx.helper.string("locationName", _locationName);
const location = Object.values(Locations).find((l) => l.name === locationName);
if (!location) {
workerScript.log("goToLocation", () => `No location named ${locationName}`);
@ -256,10 +258,10 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
universityCourse: (_ctx: NetscriptContext) =>
function (_universityName: unknown, _className: unknown, _focus: unknown = true): boolean {
_ctx.helper.checkSingularityAccess();
const universityName = _ctx.helper.string("universityName", _universityName);
const className = _ctx.helper.string("className", _className);
const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess();
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
@ -347,10 +349,10 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
gymWorkout: (_ctx: NetscriptContext) =>
function (_gymName: unknown, _stat: unknown, _focus: unknown = true): boolean {
_ctx.helper.checkSingularityAccess();
const gymName = _ctx.helper.string("gymName", _gymName);
const stat = _ctx.helper.string("stat", _stat);
const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess();
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
@ -462,8 +464,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
travelToCity: (_ctx: NetscriptContext) =>
function (_cityName: unknown): boolean {
const cityName = _ctx.helper.city("cityName", _cityName);
_ctx.helper.checkSingularityAccess();
const cityName = _ctx.helper.city("cityName", _cityName);
switch (cityName) {
case CityName.Aevum:
@ -520,8 +522,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
purchaseProgram: (_ctx: NetscriptContext) =>
function (_programName: unknown): boolean {
const programName = _ctx.helper.string("programName", _programName).toLowerCase();
_ctx.helper.checkSingularityAccess();
const programName = _ctx.helper.string("programName", _programName).toLowerCase();
if (!player.hasTorRouter()) {
workerScript.log("purchaseProgram", () => "You do not have the TOR router.");
@ -569,8 +571,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
connect: (_ctx: NetscriptContext) =>
function (_hostname: unknown): boolean {
const hostname = _ctx.helper.string("hostname", _hostname);
_ctx.helper.checkSingularityAccess();
const hostname = _ctx.helper.string("hostname", _hostname);
if (!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}'`);
}
//Home case
if (hostname === "home") {
player.getCurrentServer().isConnectedTo = false;
player.currentServer = player.getHomeComputer().hostname;
@ -588,6 +591,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return true;
}
//Adjacent server case
const server = player.getCurrentServer();
for (let i = 0; i < server.serversOnNetwork.length; 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;
},
manualHack: (_ctx: NetscriptContext) =>
@ -649,8 +664,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
setFocus: (_ctx: NetscriptContext) =>
function (_focus: unknown): boolean {
const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess();
const focus = _ctx.helper.boolean(_focus);
if (!player.isWorking) {
throw _ctx.helper.makeRuntimeErrorMsg("Not currently working");
}
@ -849,9 +864,9 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
workForCompany: (_ctx: NetscriptContext) =>
function (_companyName: unknown, _focus: unknown = true): boolean {
_ctx.helper.checkSingularityAccess();
let companyName = _ctx.helper.string("companyName", _companyName);
const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess();
// Sanitize input
if (companyName == null) {
@ -905,9 +920,9 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
applyToCompany: (_ctx: NetscriptContext) =>
function (_companyName: unknown, _field: unknown): boolean {
_ctx.helper.checkSingularityAccess();
const companyName = _ctx.helper.string("companyName", _companyName);
const field = _ctx.helper.string("field", _field);
_ctx.helper.checkSingularityAccess();
getCompany(_ctx, companyName);
player.location = companyName as LocationName;
@ -977,22 +992,22 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
getCompanyRep: (_ctx: NetscriptContext) =>
function (_companyName: unknown): number {
const companyName = _ctx.helper.string("companyName", _companyName);
_ctx.helper.checkSingularityAccess();
const companyName = _ctx.helper.string("companyName", _companyName);
const company = getCompany(_ctx, companyName);
return company.playerReputation;
},
getCompanyFavor: (_ctx: NetscriptContext) =>
function (_companyName: unknown): number {
const companyName = _ctx.helper.string("companyName", _companyName);
_ctx.helper.checkSingularityAccess();
const companyName = _ctx.helper.string("companyName", _companyName);
const company = getCompany(_ctx, companyName);
return company.favor;
},
getCompanyFavorGain: (_ctx: NetscriptContext) =>
function (_companyName: unknown): number {
const companyName = _ctx.helper.string("companyName", _companyName);
_ctx.helper.checkSingularityAccess();
const companyName = _ctx.helper.string("companyName", _companyName);
const company = getCompany(_ctx, companyName);
return company.getFavorGain();
},
@ -1004,8 +1019,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
joinFaction: (_ctx: NetscriptContext) =>
function (_facName: unknown): boolean {
const facName = _ctx.helper.string("facName", _facName);
_ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
getFaction(_ctx, facName);
if (!player.factionInvitations.includes(facName)) {
@ -1028,10 +1043,10 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
workForFaction: (_ctx: NetscriptContext) =>
function (_facName: unknown, _type: unknown, _focus: unknown = true): boolean {
_ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
const type = _ctx.helper.string("type", _type);
const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess();
getFaction(_ctx, facName);
// 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) =>
function (_facName: unknown): number {
const facName = _ctx.helper.string("facName", _facName);
_ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
const faction = getFaction(_ctx, facName);
return faction.playerReputation;
},
getFactionFavor: (_ctx: NetscriptContext) =>
function (_facName: unknown): number {
const facName = _ctx.helper.string("facName", _facName);
_ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
const faction = getFaction(_ctx, facName);
return faction.favor;
},
getFactionFavorGain: (_ctx: NetscriptContext) =>
function (_facName: unknown): number {
const facName = _ctx.helper.string("facName", _facName);
_ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
const faction = getFaction(_ctx, facName);
return faction.getFavorGain();
},
donateToFaction: (_ctx: NetscriptContext) =>
function (_facName: unknown, _amt: unknown): boolean {
_ctx.helper.checkSingularityAccess();
const facName = _ctx.helper.string("facName", _facName);
const amt = _ctx.helper.number("amt", _amt);
_ctx.helper.checkSingularityAccess();
const faction = getFaction(_ctx, facName);
if (!player.factions.includes(faction.name)) {
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) =>
function (_programName: unknown, _focus: unknown = true): boolean {
_ctx.helper.checkSingularityAccess();
const programName = _ctx.helper.string("programName", _programName).toLowerCase();
const focus = _ctx.helper.boolean(_focus);
_ctx.helper.checkSingularityAccess();
const wasFocusing = player.focus;
if (player.isWorking) {
@ -1236,8 +1251,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
commitCrime: (_ctx: NetscriptContext) =>
function (_crimeRoughName: unknown): number {
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
_ctx.helper.checkSingularityAccess();
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
if (player.isWorking) {
const txt = player.singularityStopWork();
@ -1257,8 +1272,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
getCrimeChance: (_ctx: NetscriptContext) =>
function (_crimeRoughName: unknown): number {
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
_ctx.helper.checkSingularityAccess();
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
const crime = findCrime(crimeRoughName.toLowerCase());
if (crime == null) {
@ -1269,8 +1284,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
getCrimeStats: (_ctx: NetscriptContext) =>
function (_crimeRoughName: unknown): CrimeStats {
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
_ctx.helper.checkSingularityAccess();
const crimeRoughName = _ctx.helper.string("crimeRoughName", _crimeRoughName);
const crime = findCrime(crimeRoughName.toLowerCase());
if (crime == null) {
@ -1292,8 +1307,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
},
getDarkwebProgramCost: (_ctx: NetscriptContext) =>
function (_programName: unknown): number {
const programName = _ctx.helper.string("programName", _programName).toLowerCase();
_ctx.helper.checkSingularityAccess();
const programName = _ctx.helper.string("programName", _programName).toLowerCase();
// If we don't have Tor, log it and return -1
if (!player.hasTorRouter()) {
@ -1322,5 +1337,51 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}
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 { getRamCost } from "../Netscript/RamCostGenerator";
import { FactionWorkType } from "../Faction/FactionWorkTypeEnum";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { WorkerScript } from "../Netscript/WorkerScript";
import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers";
@ -20,7 +19,7 @@ import {
export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): ISleeve {
const checkSleeveAPIAccess = function (func: string): void {
if (player.bitNodeN !== 10 && !SourceFileFlags[10]) {
if (player.bitNodeN !== 10 && !player.sourceFileLvl(10)) {
throw helper.makeRuntimeErrorMsg(
`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",

@ -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 { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions";
import { IPlayer } from "../../IPlayer";
import { getGraftingAvailableAugs } from "../GraftingHelpers";
import { GraftableAugmentation } from "../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 => {
if (player.money < aug.cost) {
return false;
@ -71,7 +60,7 @@ export const GraftingRoot = (): React.ReactElement => {
GraftableAugmentations[name] = graftableAug;
}
const [selectedAug, setSelectedAug] = useState(getAvailableAugs(player)[0]);
const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]);
const [graftOpen, setGraftOpen] = useState(false);
return (
@ -92,10 +81,10 @@ export const GraftingRoot = (): React.ReactElement => {
<Box sx={{ my: 3 }}>
<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" }}>
<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}>
<Typography>{k}</Typography>
</ListItemButton>

@ -7,7 +7,7 @@ import { Augmentation } from "../../Augmentation/Augmentation";
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;
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) {
if (owned.name === augName) {
return true;

@ -1,5 +1,4 @@
import { Bladeburner } from "../../Bladeburner/Bladeburner";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { IPlayer } from "../IPlayer";
export function canAccessBladeburner(this: IPlayer): boolean {
@ -7,7 +6,7 @@ export function canAccessBladeburner(this: IPlayer): boolean {
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 {

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

@ -1,7 +1,6 @@
import { Factions } from "../../Faction/Factions";
import { Faction } from "../../Faction/Faction";
import { Gang } from "../../Gang/Gang";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { IPlayer } from "../IPlayer";
// 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) {
return true;
}
if (SourceFileFlags[2] <= 0) {
if (this.sourceFileLvl(2) <= 0) {
return false;
}

@ -45,7 +45,6 @@ import { SpecialServers } from "../../Server/data/SpecialServers";
import { applySourceFile } from "../../SourceFile/applySourceFile";
import { applyExploit } from "../../Exploits/applyExploits";
import { SourceFiles } from "../../SourceFile/SourceFiles";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { influenceStockThroughCompanyWork } from "../../StockMarket/PlayerInfluencing";
import { getHospitalizationCost } from "../../Hospital/Hospital";
import { WorkerScript } from "../../Netscript/WorkerScript";
@ -121,7 +120,7 @@ export function prestigeAugmentation(this: PlayerObject): void {
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;
for (let i = this.sleeves.length; i < numSleeves; i++) {
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()");
return;
}
if (SourceFileFlags[5] > 0 || this.intelligence > 0) {
if (this.sourceFileLvl(5) > 0 || this.intelligence > 0) {
this.intelligence_exp += 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
let bn11Mult = 1;
const company = Companies[this.companyName];
if (SourceFileFlags[11] > 0) {
if (this.sourceFileLvl(11) > 0) {
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 {
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.`);
if (!this.getHomeComputer().programs.includes(programName)) {
this.getHomeComputer().programs.push(programName);
} else {
}
} else if (!this.getHomeComputer().programs.includes(programName)) {
//Incomplete case
const perc = (Math.floor((this.timeWorkedCreateProgram / this.timeNeededToCompleteWork) * 10000) / 100).toString();
const incompleteName = programName + "-" + perc + "%-INC";
this.getHomeComputer().programs.push(incompleteName);
}
if (!cancelled) {
this.gainIntelligenceExp((CONSTANTS.IntelligenceProgramBaseExpGain * this.timeWorked) / 1000);
}
this.isWorking = false;
this.resetWorkStatus();
@ -2092,12 +2092,7 @@ export function reapplyAllAugmentations(this: IPlayer, resetMultipliers = true):
const playerAug = this.augmentations[i];
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) {
for (let j = 0; j < playerAug.level; ++j) {
applyAugmentation(this.augmentations[i], true);
@ -2718,7 +2713,7 @@ export function gotoLocation(this: IPlayer, to: LocationName): 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 {
@ -2756,7 +2751,7 @@ export function setMult(this: IPlayer, name: string, mult: number): void {
}
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 {

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

@ -24,9 +24,4 @@ export class Program {
this.create = create;
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 { prestigeSourceFile } from "./Prestige";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { SourceFiles } from "./SourceFile/SourceFiles";
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 {
if (!flume) {
giveSourceFile(destroyedBitNode);
} else if (SourceFileFlags[5] === 0 && newBitNode !== 5) {
} else if (Player.sourceFileLvl(5) === 0 && newBitNode !== 5) {
Player.intelligence = 0;
Player.intelligence_exp = 0;
}

@ -3,11 +3,9 @@ import { Companies, loadCompanies } from "./Company/Companies";
import { CONSTANTS } from "./Constants";
import { Factions, loadFactions } from "./Faction/Factions";
import { loadAllGangs, AllGangs } from "./Gang/AllGangs";
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
import { Player, loadPlayer } from "./Player";
import { saveAllServers, loadAllServers, GetAllServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
import { staneksGift, loadStaneksGift } from "./CotMG/Helper";
@ -67,7 +65,6 @@ class BitburnerSaveObject {
FactionsSave = "";
AliasesSave = "";
GlobalAliasesSave = "";
MessagesSave = "";
StockMarketSave = "";
SettingsSave = "";
VersionSave = "";
@ -83,7 +80,6 @@ class BitburnerSaveObject {
this.FactionsSave = JSON.stringify(Factions);
this.AliasesSave = JSON.stringify(Aliases);
this.GlobalAliasesSave = JSON.stringify(GlobalAliases);
this.MessagesSave = JSON.stringify(Messages);
this.StockMarketSave = JSON.stringify(StockMarket);
this.SettingsSave = JSON.stringify(Settings);
this.VersionSave = JSON.stringify(CONSTANTS.VersionNumber);
@ -129,7 +125,7 @@ class BitburnerSaveObject {
// Save file name is based on current timestamp and BitNode
const epochTime = Math.round(Date.now() / 1000);
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;
return filename;
}
@ -398,6 +394,9 @@ function evaluateVersionCompatibility(ver: string | number): void {
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`);
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")) {
try {
loadStockMarket(saveObj.StockMarketSave);

@ -2386,6 +2386,30 @@ export interface Singularity {
* purchased. Throws an error if the specified program/exploit does not exist
*/
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.
*
* @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 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.
@ -3249,7 +3273,7 @@ export interface CodingContract {
* Returns a name describing the type of problem posed by the Coding Contract.
* (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.
* @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.
*
* @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.
* @returns Contracts text description.
*/
@ -3290,7 +3314,7 @@ export interface CodingContract {
*
* 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.
* @returns How many attempts are remaining for the contract;
*/
@ -3784,6 +3808,18 @@ export interface Grafting {
*/
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.
* @remarks
@ -4693,9 +4729,11 @@ export interface NS {
* 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 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.
*/
growthAnalyzeSecurity(threads: number): number;
growthAnalyzeSecurity(threads: number, hostname?: string, cores?: number): number;
/**
* 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.
* The PID will always be a positive integer.
*
* Running this function with a numThreads argument of 0 will return 0 without running the script.
* However, running this function with a negative numThreads argument will cause a runtime error.
* Running this function with 0 or a negative numThreads argument will cause a runtime error.
*
* @example
* ```ts
@ -6781,8 +6818,9 @@ export interface WarehouseAPI {
* Upgrade warehouse
* @param divisionName - Name of the division
* @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
* @param divisionName - Name of the division
@ -6798,6 +6836,22 @@ export interface WarehouseAPI {
designInvest: number,
marketingInvest: number,
): 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
* @returns cost
@ -6805,9 +6859,12 @@ export interface WarehouseAPI {
getPurchaseWarehouseCost(): number;
/**
* 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
*/
getUpgradeWarehouseCost(adivisionName: any, acityName: any): number;
getUpgradeWarehouseCost(adivisionName: any, acityName: any, amt?: number): number;
/**
* Check if you have a warehouse in city
* @returns true if warehouse is present, false if not
@ -7053,6 +7110,8 @@ interface Material {
prod: number;
/** Amount of material sold */
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 MenuItem from "@mui/material/MenuItem";
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 {
options: Options;
@ -23,6 +27,7 @@ export function OptionsModal(props: IProps): React.ReactElement {
const [fontSize, setFontSize] = useState(props.options.fontSize);
const [wordWrap, setWordWrap] = useState(props.options.wordWrap);
const [vim, setVim] = useState(props.options.vim);
const [themeEditorOpen, setThemeEditorOpen] = useState(false);
function save(): void {
props.save({
@ -43,6 +48,7 @@ export function OptionsModal(props: IProps): React.ReactElement {
return (
<Modal open={props.open} onClose={props.onClose}>
<ThemeEditorModal open={themeEditorOpen} onClose={() => setThemeEditorOpen(false)} />
<Box display="flex" flexDirection="row" alignItems="center">
<Typography>Theme: </Typography>
<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="dracula">dracula</MenuItem>
<MenuItem value="one-dark">one-dark</MenuItem>
<MenuItem value="customTheme">Custom theme</MenuItem>
</Select>
<Button onClick={() => setThemeEditorOpen(true)} sx={{ mx: 1 }} startIcon={<EditIcon />}>
Edit custom theme
</Button>
</Box>
<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} />
</Box>
<br />
<Button onClick={save}>Save</Button>
<Button onClick={save} startIcon={<SaveIcon />}>
Save
</Button>
</Modal>
);
}

@ -25,7 +25,7 @@ import { Settings } from "../../Settings/Settings";
import { iTutorialNextStep, ITutorial, iTutorialSteps } from "../../InteractiveTutorial";
import { debounce } from "lodash";
import { saveObject } from "../../SaveObject";
import { loadThemes } from "./themes";
import { loadThemes, makeTheme, sanitizeTheme } from "./themes";
import { GetServer } from "../../Server/AllServers";
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.typescriptDefaults.addExtraLib(source, "netscript.d.ts");
loadThemes(monaco);
sanitizeTheme(Settings.EditorTheme);
monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
}
// When the editor is mounted
@ -993,7 +995,11 @@ export function Root(props: IProps): React.ReactElement {
</Box>
<OptionsModal
open={optionsOpen}
onClose={() => setOptionsOpen(false)}
onClose={() => {
sanitizeTheme(Settings.EditorTheme);
monacoRef.current?.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
setOptionsOpen(false);
}}
options={{
theme: Settings.MonacoTheme,
insertSpaces: Settings.MonacoInsertSpaces,
@ -1002,6 +1008,8 @@ export function Root(props: IProps): React.ReactElement {
vim: Settings.MonacoVim,
}}
save={(options: Options) => {
sanitizeTheme(Settings.EditorTheme);
monacoRef.current?.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
setOptions(options);
Settings.MonacoTheme = options.theme;
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> {
monaco.editor.defineTheme("monokai", {
base: "vs-dark",
@ -261,6 +474,7 @@ export async function loadThemes(monaco: { editor: any }): Promise<void> {
foreground: "FFB86C",
fontStyle: "italic",
},
{
token: "netscriptfunction",
foreground: "FF79C6",

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

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

@ -51,3 +51,14 @@ export function getSubdirectories(serv: BaseServer, dir: string): string[] {
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 { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer";
import { showMessage } from "../../Message/MessageHelpers";
import { MessageFilenames, showMessage } from "../../Message/MessageHelpers";
import { showLiterature } from "../../Literature/LiteratureHelpers";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { checkEnum } from "../../utils/helpers/checkEnum";
export function cat(
terminal: ITerminal,
@ -43,6 +44,7 @@ export function cat(
} else if (filename.endsWith(".msg")) {
const file = server.messages[i];
if (file !== filename) continue;
if (!checkEnum(MessageFilenames, file)) return;
showMessage(file);
return;
}

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

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

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