merge base

This commit is contained in:
phyzical 2022-03-30 08:18:16 +08:00
commit cee53ee1a7
84 changed files with 1994 additions and 959 deletions

154
dist/bitburner.d.ts vendored

@ -983,6 +983,16 @@ export declare interface Corporation extends WarehouseAPI, OfficeAPI {
* *
*/ */
sellShares(amount: number): void; sellShares(amount: number): void;
/**
* Get bonus time.
*
* Bonus time is accumulated when the game is offline or if the game is inactive in the browser.
*
* Bonus time makes the game progress faster.
*
* @returns Bonus time for the Corporation mechanic in milliseconds.
*/
getBonusTime(): number;
} }
/** /**
@ -1716,6 +1726,49 @@ export declare interface GangTerritory {
wanted: number; wanted: number;
} }
/**
* Grafting API
* @remarks
* This API requires Source-File 10 to use.
* @public
*/
export declare interface Grafting {
/**
* Retrieve the grafting cost of an aug.
* @remarks
* RAM cost: 3.75 GB
*
* @param augName - Name of the aug to check the price of. Must be an exact match.
* @returns The cost required to graft the named augmentation.
* @throws Will error if an invalid Augmentation name is provided.
*/
getAugmentationGraftPrice(augName: string): number;
/**
* Retrieves the time required to graft an aug.
* @remarks
* RAM cost: 3.75 GB
*
* @param augName - Name of the aug to check the grafting time of. Must be an exact match.
* @returns The time required, in millis, to graft the named augmentation.
* @throws Will error if an invalid Augmentation name is provided.
*/
getAugmentationGraftTime(augName: string): number;
/**
* Begins grafting the named aug. You must be in New Tokyo to use this.
* @remarks
* RAM cost: 7.5 GB
*
* @param augName - The name of the aug to begin grafting. Must be an exact match.
* @param focus - Acquire player focus on this Augmentation grafting. Optional. Defaults to true.
* @returns True if the aug successfully began grafting, false otherwise (e.g. not enough money, or
* invalid Augmentation name provided).
* @throws Will error if called while you are not in New Tokyo.
*/
graftAugmentation(augName: string, focus?: boolean): boolean;
}
/** /**
* Hacking formulas * Hacking formulas
* @public * @public
@ -2582,6 +2635,13 @@ export declare interface NS extends Singularity {
*/ */
readonly ui: UserInterface; readonly ui: UserInterface;
/**
* Namespace for grafting functions.
* @remarks
* RAM cost: 0 GB
*/
readonly grafting: Grafting;
/** /**
* Arguments passed into the script. * Arguments passed into the script.
* *
@ -4833,6 +4893,7 @@ export declare interface Player {
tor: boolean; tor: boolean;
hasCorporation: boolean; hasCorporation: boolean;
inBladeburner: boolean; inBladeburner: boolean;
entropy: number;
} }
/** /**
@ -5088,7 +5149,7 @@ export declare interface Singularity {
* purchasing a TOR router using this function is the same as if you were to * purchasing a TOR router using this function is the same as if you were to
* manually purchase one. * manually purchase one.
* *
* @returns True if actions is successful, false otherwise. * @returns True if actions is successful or you already own TOR router, false otherwise.
*/ */
purchaseTor(): boolean; purchaseTor(): boolean;
@ -5780,6 +5841,67 @@ export declare interface Singularity {
* @returns True if the focus was changed. * @returns True if the focus was changed.
*/ */
setFocus(focus: boolean): boolean; setFocus(focus: boolean): boolean;
/**
* Get a list of programs offered on the dark web.
* @remarks
* RAM cost: 1 GB * 16/4/1
*
*
* This function allows the player to get a list of programs available for purchase
* on the dark web. Players MUST have purchased Tor to get the list of programs
* available. If Tor has not been purchased yet, this function will return an
* empty list.
*
* @example
* ```ts
* // NS1
* getDarkwebProgramsAvailable();
* // returns ['BruteSSH.exe', 'FTPCrack.exe'...etc]
* ```
* @example
* ```ts
* // NS2
* ns.getDarkwebProgramsAvailable();
* // returns ['BruteSSH.exe', 'FTPCrack.exe'...etc]
* ```
* @returns - a list of programs available for purchase on the dark web, or [] if Tor has not
* been purchased
*/
getDarkwebPrograms(): string[];
/**
* Check the price of an exploit on the dark web
* @remarks
* RAM cost: 0.5 GB * 16/4/1
*
*
* This function allows you to check the price of a darkweb exploit/program.
* You MUST have a TOR router in order to use this function. The price returned
* by this function is the same price you would see with buy -l from the terminal.
* Returns the cost of the program if it has not been purchased yet, 0 if it
* has already been purchased, or -1 if Tor has not been purchased (and thus
* the program/exploit is not available for purchase).
*
* If the program does not exist, an error is thrown.
*
*
* @example
* ```ts
* // NS1
* getDarkwebProgramCost("brutessh.exe");
* ```
* @example
* ```ts
* // NS2
* ns.getDarkwebProgramCost("brutessh.exe");
* ```
* @param programName - Name of program to check the price of
* @returns Price of the specified darkweb program
* (if not yet purchased), 0 if it has already been purchased, or -1 if Tor has not been
* purchased. Throws an error if the specified program/exploit does not exist
*/
getDarkwebProgramCost(programName: string): number;
} }
/** /**
@ -6114,14 +6236,14 @@ export declare interface Stanek {
* RAM cost: 0.4 GB * RAM cost: 0.4 GB
* @returns The width of the gift. * @returns The width of the gift.
*/ */
width(): number; giftWidth(): number;
/** /**
* Stanek's Gift height. * Stanek's Gift height.
* @remarks * @remarks
* RAM cost: 0.4 GB * RAM cost: 0.4 GB
* @returns The height of the gift. * @returns The height of the gift.
*/ */
height(): number; giftHeight(): number;
/** /**
* Charge a fragment, increasing its power. * Charge a fragment, increasing its power.
@ -6131,7 +6253,7 @@ export declare interface Stanek {
* @param rootY - rootY Root Y against which to align the top left of the fragment. * @param rootY - rootY Root Y against which to align the top left of the fragment.
* @returns Promise that lasts until the charge action is over. * @returns Promise that lasts until the charge action is over.
*/ */
charge(rootX: number, rootY: number): Promise<void>; chargeFragment(rootX: number, rootY: number): Promise<void>;
/** /**
* List possible fragments. * List possible fragments.
@ -6156,7 +6278,7 @@ export declare interface Stanek {
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0 GB
*/ */
clear(): void; clearGift(): void;
/** /**
* Check if fragment can be placed at specified location. * Check if fragment can be placed at specified location.
@ -6169,7 +6291,7 @@ export declare interface Stanek {
* @param fragmentId - fragmentId ID of the fragment to place. * @param fragmentId - fragmentId ID of the fragment to place.
* @returns true if the fragment can be placed at that position. false otherwise. * @returns true if the fragment can be placed at that position. false otherwise.
*/ */
canPlace(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean; canPlaceFragment(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
/** /**
* Place fragment on Stanek's Gift. * Place fragment on Stanek's Gift.
* @remarks * @remarks
@ -6181,7 +6303,7 @@ export declare interface Stanek {
* @param fragmentId - ID of the fragment to place. * @param fragmentId - ID of the fragment to place.
* @returns true if the fragment can be placed at that position. false otherwise. * @returns true if the fragment can be placed at that position. false otherwise.
*/ */
place(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean; placeFragment(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
/** /**
* Get placed fragment at location. * Get placed fragment at location.
* @remarks * @remarks
@ -6191,7 +6313,7 @@ export declare interface Stanek {
* @param rootY - Y against which to align the top left of the fragment. * @param rootY - Y against which to align the top left of the fragment.
* @returns The fragment at [rootX, rootY], if any. * @returns The fragment at [rootX, rootY], if any.
*/ */
get(rootX: number, rootY: number): ActiveFragment | undefined; getFragment(rootX: number, rootY: number): ActiveFragment | undefined;
/** /**
* Remove fragment at location. * Remove fragment at location.
@ -6202,7 +6324,7 @@ export declare interface Stanek {
* @param rootY - Y against which to align the top left of the fragment. * @param rootY - Y against which to align the top left of the fragment.
* @returns The fragment at [rootX, rootY], if any. * @returns The fragment at [rootX, rootY], if any.
*/ */
remove(rootX: number, rootY: number): boolean; removeFragment(rootX: number, rootY: number): boolean;
} }
/** /**
@ -6591,6 +6713,20 @@ export declare interface TIX {
* @returns True if you successfully purchased it or if you already have access, false otherwise. * @returns True if you successfully purchased it or if you already have access, false otherwise.
*/ */
purchase4SMarketDataTixApi(): boolean; purchase4SMarketDataTixApi(): boolean;
/**
* Purchase WSE Account.
* @remarks RAM cost: 2.5 GB
* @returns True if you successfully purchased it or if you already have access, false otherwise.
*/
purchaseWseAccount(): boolean;
/**
* Purchase TIX API Access
* @remarks RAM cost: 2.5 GB
* @returns True if you successfully purchased it or if you already have access, false otherwise.
*/
purchaseTixApi(): boolean;
} }
/** /**

54
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

@ -12,4 +12,5 @@ must be unlocked.
Source-Files <advancedgameplay/sourcefiles> Source-Files <advancedgameplay/sourcefiles>
Intelligence <advancedgameplay/intelligence> Intelligence <advancedgameplay/intelligence>
Sleeves <advancedgameplay/sleeves> Sleeves <advancedgameplay/sleeves>
Grafting <advancedgameplay/grafting>
Hacking algorithms <advancedgameplay/hackingalgorithms> Hacking algorithms <advancedgameplay/hackingalgorithms>

@ -0,0 +1,18 @@
.. _gameplay_grafting:
Grafting
========
Grafting is an experimental process through which you can obtain the benefits of
Augmentations, without needing to reboot your body.
In order to graft, you must first purchase a blueprint for and craft the Augmentation.
This can be done at VitaLife in New Tokyo, where you'll find a shady researcher with
questionable connections. Once you purchase a blueprint, you will start crafting the
Augmentation, and it will be grafted to your body once complete.
Be warned, some who have tested grafting have reported an unidentified malware. Dubbed
"Entropy", this virus seems to grow in potency as more Augmentations are grafted,
causing unpredictable affects to the victim.
Note that when crafting an Augmentation, cancelling will **not** save your progress,
and the money spent will **not** be returned.

@ -16,3 +16,4 @@ Intelligence will boost your production for many actions in the game, including:
* Crime success rate * Crime success rate
* Bladeburner * Bladeburner
* Reputation gain for companies & factions * Reputation gain for companies & factions
* Augmentation grafting speed

@ -91,19 +91,3 @@ and above, and is only available after defeating BitNode-10 at least once.
Memory is a persistent stat, meaning it never gets reset back to 1. Memory is a persistent stat, meaning it never gets reset back to 1.
The maximum possible value for a sleeve's memory is 100. The maximum possible value for a sleeve's memory is 100.
Re-sleeving
^^^^^^^^^^^
Re-sleeving is the process of digitizing and transferring your consciousness into a
new human body, or "sleeve". When you re-sleeve into a new body, your stat experience
and Augmentations get replaced with those of the new body.
In order to re-sleeve, you must purchase new bodies. This can be done at VitaLife in
New Tokyo. Once you purchase a body to re-sleeve into, the effects will take
place immediately.
Note that resleeving **REMOVES** all of your currently-installed Augmentations,
and replaces them with the ones provided by the purchased sleeve. However,
Augmentations that are purchased but not installed will **not** be removed. If you have purchased
an Augmentation and then re-sleeve into a body which already has that Augmentation,
it will be removed since you cannot have duplicate Augmentations.

@ -53,6 +53,7 @@ List of all Source-Files
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|| BitNode-10: Digital Carbon || * Each level of this grants a Duplicate Sleeve. | || BitNode-10: Digital Carbon || * Each level of this grants a Duplicate Sleeve. |
|| || * Allows the player to access the `Sleeve API <https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.sleeve.md>`_ in other BitNodes. | || || * Allows the player to access the `Sleeve API <https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.sleeve.md>`_ in other BitNodes. |
|| || * Grants the player access to the VitaLife secret laboratory in other BitNodes. Also grants access to the Grafting API. |
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|| BitNode-11: The Big Crash || * Company favor increases both the player's salary and reputation gain at that | || BitNode-11: The Big Crash || * Company favor increases both the player's salary and reputation gain at that |
|| || company by 1% per favor (rather than just the reputation gain). | || || company by 1% per favor (rather than just the reputation gain). |

@ -3,6 +3,108 @@
Changelog Changelog
========= =========
v1.6.0 - 2022-03-29 Grafting
----------------------------
** Vitalife secret lab **
* A new mechanic called Augmentation Grafting has been added. Resleeving has been removed.
* Credit to @nickofolas for his incredible work.
** Stanek **
* BREAKING: Many functions in the stanek API were renamed in order to avoid name collision with things like Map.prototype.get
** UI **
* Major update to Sleeve, Gang UI, and Create Program (@nickofolas)
* re-add pre tags to support slash n in prompt (@jacktose)
* Tabelize linked output of 'ls' (@Master-Guy)
* Add the ability to filter open scripts (@phyzical)
* Add minHeight to editor tabs (@nickofolas)
* Properly expand gang equipment cards to fill entire screen (@nickofolas)
* Add shortcut to Faction augmentations page from FactionsRoot (@nickofolas)
* Fix extra space on editor tabs (@nickofolas)
* Present offline message as list (@DSteve595)
* add box showing remaining augments per faction (@jjayeon)
* Add tab switching support to vim mode (@JParisFerrer)
* Show current task on gang management screen (@zeddrak)
* Fix for ui of gang members current task when set via api (@phyzical)
* Don't hide irrelevant materials if their stock is not empty and hide irrelevant divisions from Export (@SagePtr)
* Fix regex to enable alpha transparency hex codes (8 digits) (@surdaft)
** API **
* Added dark web functions to ns api
* BREAKING: purchaseTor() should returns true if player already has Tor. (@DavidGrinberg, @waffleattack)
* Implement getBonusTime in Corporation API (@t-wolfeadam)
* Added functions to purchase TIX and WSI (@incubusnb)
* purchaseSleeveAug checks shock value (@incubusnb)
* Fix bug with hacknet api
* Fix spendHashes bug
* Added 0 cost of asleep() (@Master-Guy)
* Fix some misleading corporation errors (@TheRealMaxion)
* expose the inBladeburner on the player object (@phyzical)
* added ram charge for stanek width and height (@phyzical)
* Fix sufficient player money check to buy back shares. (@ChrissiQ)
* Fix Static Ram Circumventing for some NS functions (@CrafterKolyan)
* added CorporationSoftCap to NetscriptDefinitions (@phyzical)
* Added definition of autocomplete() 'data' argument. (@tigercat2000)
* Adding support for text/select options in Prompt command (@PhilipArmstead)
* Added the ability to exportGame via api (@phyzical)
** Arcade **
* Added an arcade to New Tokyo where you can play a 4 year old version of bitburner.
** Misc. **
* Add a warning triggered while auto-saves are off. (@MartinFournier)
* Log info for field analysis now displays actual rank gained. (@ApamNapat)
* Removed BladeburnerSkillCost from skill point cost description. (@ApamNapat)
* Fix handling for UpArrow in bladeburner console. (@dowinter)
* Add GitHub action to check PRs for generated files. (@MartinFournier)
* Cap Staneks gift at 25x25 to prevent crashes. (@waffleattack)
* Remove old & unused files from repository. (@MartinFournier)
* Factions on the factions screens are sorted by story progress / type. (@phyzical)
* Fix log manager not picking up new runs of scripts. (@phyzical)
* Added prettier to cicd.
* UI improvements (@phyzical)
* Documentation / Typos (@nanogyth, @Master-Guy, @incubusnb, @ApamNapat, @phyzical, @SagePtr)
* Give player code a copy of Division.upgrades instead of the live object (@Ornedan)
* Fix bug with small town achievement.
* Fix bug with purchaseSleeveAug (@phyzical)
* Check before unlocking corp upgrade (@gianfun)
* General codebase improvements. (@phyzical, @Master-Guy, @ApamNapat)
* Waiting on promises in NS1 no longer freezes the script. (@Master-Guy)
* Fix bug with missing ramcost for tFormat (@TheMas3212)
* Fix crash with new prompt
* Quick fix to prevent division by 0 in terminal (@Master-Guy)
* removed ip references (@phyzical, @Master-Guy)
* Terminal now supports 'ls -l'
* Fix negative number formatting (@Master-Guy)
* Fix unique ip generation (@InDieTasten)
* remove terminal command theme from docs (@phyzical)
* Fix 'Augmentations Left' with gang factions (@nickofolas)
* Attempt to fix 'bladeburner.process()' early routing issue (@MartinFournier)
* work in progress augment fix (@phyzical)
* Fixes missing space in Smart Supply (@TheRealMaxion)
* Change license to Apache 2 with Commons Clause
* updated regex sanitization (@mbrannen)
* Sleeve fix for when faction isnt found (@phyzical)
* Fix editor "close" naming (@phyzical)
* Fix bug with sleeves where some factions would be listed as workable. (@phyzical)
* Fix research tree of product industries post-prestige (@pd)
* Added a check for exisiting industry type before expanding (@phyzical)
* fix hackAnalyzeThreads returning infinity (@chrisrabe)
* Make growthAnalyze more accurate (@dwRchyngqxs)
* Add 'Zoom -> Reset Zoom' command to Steam (@smolgumball)
* Add hasOwnProperty check to GetServer (@SagePtr)
* Speed up employee productivity calculation (@pd)
* Field Work and Security Work benefit from 'share' (@SagePtr)
* Nerf noodle bar.
v1.5.0 - Steam Cloud integration v1.5.0 - Steam Cloud integration
-------------------------------- --------------------------------
@ -21,7 +123,7 @@ v1.5.0 - Steam Cloud integration
* Fix display of corporation product equation (@SagePtr) * Fix display of corporation product equation (@SagePtr)
* Make Bitverse more accessible (@ChrissiQ) * Make Bitverse more accessible (@ChrissiQ)
* Make corporation warehouse more accessible (@ChrissiQ) * Make corporation warehouse more accessible (@ChrissiQ)
* Make tab style more consistent (@nikfolas) * Make tab style more consistent (@nickofolas)
** Netscript ** ** Netscript **

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Corporation](./bitburner.corporation.md) &gt; [getBonusTime](./bitburner.corporation.getbonustime.md)
## Corporation.getBonusTime() method
Get bonus time.
“Bonus time” is accumulated when the game is offline or if the game is inactive in the browser.
“Bonus time” makes the game progress faster.
<b>Signature:</b>
```typescript
getBonusTime(): number;
```
<b>Returns:</b>
number
Bonus time for the Corporation mechanic in milliseconds.

@ -23,6 +23,7 @@ export interface Corporation extends WarehouseAPI, OfficeAPI
| [createCorporation(corporationName, selfFund)](./bitburner.corporation.createcorporation.md) | Create a Corporation | | [createCorporation(corporationName, selfFund)](./bitburner.corporation.createcorporation.md) | Create a Corporation |
| [expandCity(divisionName, cityName)](./bitburner.corporation.expandcity.md) | Expand to a new city | | [expandCity(divisionName, cityName)](./bitburner.corporation.expandcity.md) | Expand to a new city |
| [expandIndustry(industryType, divisionName)](./bitburner.corporation.expandindustry.md) | Expand to a new industry | | [expandIndustry(industryType, divisionName)](./bitburner.corporation.expandindustry.md) | Expand to a new industry |
| [getBonusTime()](./bitburner.corporation.getbonustime.md) | Get bonus time.<!-- -->“Bonus time” is accumulated when the game is offline or if the game is inactive in the browser.<!-- -->“Bonus time” makes the game progress faster. |
| [getCorporation()](./bitburner.corporation.getcorporation.md) | Get corporation data | | [getCorporation()](./bitburner.corporation.getcorporation.md) | Get corporation data |
| [getDivision(divisionName)](./bitburner.corporation.getdivision.md) | Get division data | | [getDivision(divisionName)](./bitburner.corporation.getdivision.md) | Get division data |
| [getExpandCityCost()](./bitburner.corporation.getexpandcitycost.md) | Gets the cost to expand into a new city | | [getExpandCityCost()](./bitburner.corporation.getexpandcitycost.md) | Gets the cost to expand into a new city |

@ -0,0 +1,34 @@
<!-- 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; [getAugmentationGraftPrice](./bitburner.grafting.getaugmentationgraftprice.md)
## Grafting.getAugmentationGraftPrice() method
Retrieve the grafting cost of an aug.
<b>Signature:</b>
```typescript
getAugmentationGraftPrice(augName: string): number;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| augName | string | Name of the aug to check the price of. Must be an exact match. |
<b>Returns:</b>
number
The cost required to graft the named augmentation.
## Exceptions
Will error if an invalid Augmentation name is provided.
## Remarks
RAM cost: 3.75 GB

@ -0,0 +1,34 @@
<!-- 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; [getAugmentationGraftTime](./bitburner.grafting.getaugmentationgrafttime.md)
## Grafting.getAugmentationGraftTime() method
Retrieves the time required to graft an aug.
<b>Signature:</b>
```typescript
getAugmentationGraftTime(augName: string): number;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| augName | string | Name of the aug to check the grafting time of. Must be an exact match. |
<b>Returns:</b>
number
The time required, in millis, to graft the named augmentation.
## Exceptions
Will error if an invalid Augmentation name is provided.
## Remarks
RAM cost: 3.75 GB

@ -0,0 +1,35 @@
<!-- 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; [graftAugmentation](./bitburner.grafting.graftaugmentation.md)
## Grafting.graftAugmentation() method
Begins grafting the named aug. You must be in New Tokyo to use this.
<b>Signature:</b>
```typescript
graftAugmentation(augName: string, focus?: boolean): boolean;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| augName | string | The name of the aug to begin grafting. Must be an exact match. |
| focus | boolean | Acquire player focus on this Augmentation grafting. Optional. Defaults to true. |
<b>Returns:</b>
boolean
True if the aug successfully began grafting, false otherwise (e.g. not enough money, or invalid Augmentation name provided).
## Exceptions
Will error if called while you are not in New Tokyo.
## Remarks
RAM cost: 7.5 GB

@ -0,0 +1,26 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Grafting](./bitburner.grafting.md)
## Grafting interface
Grafting API
<b>Signature:</b>
```typescript
export interface Grafting
```
## Remarks
This API requires Source-File 10 to use.
## Methods
| Method | Description |
| --- | --- |
| [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. |
| [graftAugmentation(augName, focus)](./bitburner.grafting.graftaugmentation.md) | Begins grafting the named aug. You must be in New Tokyo to use this. |

@ -45,6 +45,7 @@
| [GangOtherInfoObject](./bitburner.gangotherinfoobject.md) | | | [GangOtherInfoObject](./bitburner.gangotherinfoobject.md) | |
| [GangTaskStats](./bitburner.gangtaskstats.md) | Object representing data representing a gang member task. | | [GangTaskStats](./bitburner.gangtaskstats.md) | Object representing data representing a gang member task. |
| [GangTerritory](./bitburner.gangterritory.md) | | | [GangTerritory](./bitburner.gangterritory.md) | |
| [Grafting](./bitburner.grafting.md) | Grafting API |
| [HackingFormulas](./bitburner.hackingformulas.md) | Hacking formulas | | [HackingFormulas](./bitburner.hackingformulas.md) | Hacking formulas |
| [HackingMultipliers](./bitburner.hackingmultipliers.md) | Hack related multipliers. | | [HackingMultipliers](./bitburner.hackingmultipliers.md) | Hack related multipliers. |
| [Hacknet](./bitburner.hacknet.md) | Hacknet API | | [Hacknet](./bitburner.hacknet.md) | Hacknet API |

@ -0,0 +1,18 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [NS](./bitburner.ns.md) &gt; [grafting](./bitburner.ns.grafting.md)
## NS.grafting property
Namespace for grafting functions.
<b>Signature:</b>
```typescript
readonly grafting: Grafting;
```
## Remarks
RAM cost: 0 GB

@ -47,6 +47,7 @@ export async function main(ns) {
| [corporation](./bitburner.ns.corporation.md) | [Corporation](./bitburner.corporation.md) | Namespace for corporation functions. RAM cost: 0 GB | | [corporation](./bitburner.ns.corporation.md) | [Corporation](./bitburner.corporation.md) | Namespace for corporation functions. RAM cost: 0 GB |
| [formulas](./bitburner.ns.formulas.md) | [Formulas](./bitburner.formulas.md) | Namespace for formulas functions. | | [formulas](./bitburner.ns.formulas.md) | [Formulas](./bitburner.formulas.md) | Namespace for formulas functions. |
| [gang](./bitburner.ns.gang.md) | [Gang](./bitburner.gang.md) | Namespace for gang functions. | | [gang](./bitburner.ns.gang.md) | [Gang](./bitburner.gang.md) | Namespace for gang functions. |
| [grafting](./bitburner.ns.grafting.md) | [Grafting](./bitburner.grafting.md) | Namespace for grafting functions. |
| [hacknet](./bitburner.ns.hacknet.md) | [Hacknet](./bitburner.hacknet.md) | Namespace for hacknet functions. | | [hacknet](./bitburner.ns.hacknet.md) | [Hacknet](./bitburner.hacknet.md) | Namespace for hacknet functions. |
| [sleeve](./bitburner.ns.sleeve.md) | [Sleeve](./bitburner.sleeve.md) | Namespace for sleeve functions. | | [sleeve](./bitburner.ns.sleeve.md) | [Sleeve](./bitburner.sleeve.md) | Namespace for sleeve functions. |
| [stanek](./bitburner.ns.stanek.md) | [Stanek](./bitburner.stanek.md) | Namespace for stanek functions. RAM cost: 0 GB | | [stanek](./bitburner.ns.stanek.md) | [Stanek](./bitburner.stanek.md) | Namespace for stanek functions. RAM cost: 0 GB |

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Player](./bitburner.player.md) &gt; [entropy](./bitburner.player.entropy.md)
## Player.entropy property
<b>Signature:</b>
```typescript
entropy: number;
```

@ -47,6 +47,7 @@ interface Player
| [dexterity\_exp](./bitburner.player.dexterity_exp.md) | number | | | [dexterity\_exp](./bitburner.player.dexterity_exp.md) | number | |
| [dexterity\_mult](./bitburner.player.dexterity_mult.md) | number | | | [dexterity\_mult](./bitburner.player.dexterity_mult.md) | number | |
| [dexterity](./bitburner.player.dexterity.md) | number | | | [dexterity](./bitburner.player.dexterity.md) | number | |
| [entropy](./bitburner.player.entropy.md) | number | |
| [faction\_rep\_mult](./bitburner.player.faction_rep_mult.md) | number | | | [faction\_rep\_mult](./bitburner.player.faction_rep_mult.md) | number | |
| [factions](./bitburner.player.factions.md) | string\[\] | | | [factions](./bitburner.player.factions.md) | string\[\] | |
| [hacking\_chance\_mult](./bitburner.player.hacking_chance_mult.md) | number | | | [hacking\_chance\_mult](./bitburner.player.hacking_chance_mult.md) | number | |

@ -0,0 +1,50 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Singularity](./bitburner.singularity.md) &gt; [getDarkwebProgramCost](./bitburner.singularity.getdarkwebprogramcost.md)
## Singularity.getDarkwebProgramCost() method
Check the price of an exploit on the dark web
<b>Signature:</b>
```typescript
getDarkwebProgramCost(programName: string): number;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| programName | string | Name of program to check the price of |
<b>Returns:</b>
number
Price of the specified darkweb program (if not yet purchased), 0 if it has already been purchased, or -1 if Tor has not been purchased. Throws an error if the specified program/exploit does not exist
## Remarks
RAM cost: 0.5 GB \* 16/4/1
This function allows you to check the price of a darkweb exploit/program. You MUST have a TOR router in order to use this function. The price returned by this function is the same price you would see with buy -l from the terminal. Returns the cost of the program if it has not been purchased yet, 0 if it has already been purchased, or -1 if Tor has not been purchased (and thus the program/exploit is not available for purchase).
If the program does not exist, an error is thrown.
## Example 1
```ts
// NS1
getDarkwebProgramCost("brutessh.exe");
```
## Example 2
```ts
// NS2
ns.getDarkwebProgramCost("brutessh.exe");
```

@ -0,0 +1,43 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Singularity](./bitburner.singularity.md) &gt; [getDarkwebPrograms](./bitburner.singularity.getdarkwebprograms.md)
## Singularity.getDarkwebPrograms() method
Get a list of programs offered on the dark web.
<b>Signature:</b>
```typescript
getDarkwebPrograms(): string[];
```
<b>Returns:</b>
string\[\]
- a list of programs available for purchase on the dark web, or \[\] if Tor has not been purchased
## Remarks
RAM cost: 1 GB \* 16/4/1
This function allows the player to get a list of programs available for purchase on the dark web. Players MUST have purchased Tor to get the list of programs available. If Tor has not been purchased yet, this function will return an empty list.
## Example 1
```ts
// NS1
getDarkwebProgramsAvailable();
// returns ['BruteSSH.exe', 'FTPCrack.exe'...etc]
```
## Example 2
```ts
// NS2
ns.getDarkwebProgramsAvailable();
// returns ['BruteSSH.exe', 'FTPCrack.exe'...etc]
```

@ -39,6 +39,8 @@ This API requires Source-File 4 to use. The RAM cost of all these functions is m
| [getCrimeChance(crime)](./bitburner.singularity.getcrimechance.md) | Get chance to successfully commit a crime. | | [getCrimeChance(crime)](./bitburner.singularity.getcrimechance.md) | Get chance to successfully commit a crime. |
| [getCrimeStats(crime)](./bitburner.singularity.getcrimestats.md) | Get stats related to a crime. | | [getCrimeStats(crime)](./bitburner.singularity.getcrimestats.md) | Get stats related to a crime. |
| [getCurrentServer()](./bitburner.singularity.getcurrentserver.md) | Get the current server. | | [getCurrentServer()](./bitburner.singularity.getcurrentserver.md) | Get the current server. |
| [getDarkwebProgramCost(programName)](./bitburner.singularity.getdarkwebprogramcost.md) | Check the price of an exploit on the dark web |
| [getDarkwebPrograms()](./bitburner.singularity.getdarkwebprograms.md) | Get a list of programs offered on the dark web. |
| [getFactionFavor(faction)](./bitburner.singularity.getfactionfavor.md) | Get faction favor. | | [getFactionFavor(faction)](./bitburner.singularity.getfactionfavor.md) | Get faction favor. |
| [getFactionFavorGain(faction)](./bitburner.singularity.getfactionfavorgain.md) | Get faction favor gain. | | [getFactionFavorGain(faction)](./bitburner.singularity.getfactionfavorgain.md) | Get faction favor gain. |
| [getFactionRep(faction)](./bitburner.singularity.getfactionrep.md) | Get faction reputation. | | [getFactionRep(faction)](./bitburner.singularity.getfactionrep.md) | Get faction reputation. |

@ -15,7 +15,7 @@ purchaseTor(): boolean;
boolean boolean
True if actions is successful, false otherwise. True if actions is successful or you already own TOR router, false otherwise.
## Remarks ## Remarks

@ -1,15 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. --> <!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [canPlace](./bitburner.stanek.canplace.md) [Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [canPlaceFragment](./bitburner.stanek.canplacefragment.md)
## Stanek.canPlace() method ## Stanek.canPlaceFragment() method
Check if fragment can be placed at specified location. Check if fragment can be placed at specified location.
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
canPlace(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean; canPlaceFragment(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
``` ```
## Parameters ## Parameters

@ -1,15 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. --> <!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [charge](./bitburner.stanek.charge.md) [Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [chargeFragment](./bitburner.stanek.chargefragment.md)
## Stanek.charge() method ## Stanek.chargeFragment() method
Charge a fragment, increasing its power. Charge a fragment, increasing its power.
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
charge(rootX: number, rootY: number): Promise<void>; chargeFragment(rootX: number, rootY: number): Promise<void>;
``` ```
## Parameters ## Parameters

@ -1,15 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. --> <!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [clear](./bitburner.stanek.clear.md) [Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [clearGift](./bitburner.stanek.cleargift.md)
## Stanek.clear() method ## Stanek.clearGift() method
Clear the board of all fragments. Clear the board of all fragments.
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
clear(): void; clearGift(): void;
``` ```
<b>Returns:</b> <b>Returns:</b>

@ -1,15 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. --> <!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [get](./bitburner.stanek.get.md) [Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [getFragment](./bitburner.stanek.getfragment.md)
## Stanek.get() method ## Stanek.getFragment() method
Get placed fragment at location. Get placed fragment at location.
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
get(rootX: number, rootY: number): ActiveFragment | undefined; getFragment(rootX: number, rootY: number): ActiveFragment | undefined;
``` ```
## Parameters ## Parameters

@ -1,15 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. --> <!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [height](./bitburner.stanek.height.md) [Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [giftHeight](./bitburner.stanek.giftheight.md)
## Stanek.height() method ## Stanek.giftHeight() method
Stanek's Gift height. Stanek's Gift height.
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
height(): number; giftHeight(): number;
``` ```
<b>Returns:</b> <b>Returns:</b>

@ -1,15 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. --> <!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [width](./bitburner.stanek.width.md) [Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [giftWidth](./bitburner.stanek.giftwidth.md)
## Stanek.width() method ## Stanek.giftWidth() method
Stanek's Gift width. Stanek's Gift width.
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
width(): number; giftWidth(): number;
``` ```
<b>Returns:</b> <b>Returns:</b>

@ -17,13 +17,13 @@ interface Stanek
| Method | Description | | Method | Description |
| --- | --- | | --- | --- |
| [activeFragments()](./bitburner.stanek.activefragments.md) | List of fragments in Stanek's Gift. | | [activeFragments()](./bitburner.stanek.activefragments.md) | List of fragments in Stanek's Gift. |
| [canPlace(rootX, rootY, rotation, fragmentId)](./bitburner.stanek.canplace.md) | Check if fragment can be placed at specified location. | | [canPlaceFragment(rootX, rootY, rotation, fragmentId)](./bitburner.stanek.canplacefragment.md) | Check if fragment can be placed at specified location. |
| [charge(rootX, rootY)](./bitburner.stanek.charge.md) | Charge a fragment, increasing its power. | | [chargeFragment(rootX, rootY)](./bitburner.stanek.chargefragment.md) | Charge a fragment, increasing its power. |
| [clear()](./bitburner.stanek.clear.md) | Clear the board of all fragments. | | [clearGift()](./bitburner.stanek.cleargift.md) | Clear the board of all fragments. |
| [fragmentDefinitions()](./bitburner.stanek.fragmentdefinitions.md) | List possible fragments. | | [fragmentDefinitions()](./bitburner.stanek.fragmentdefinitions.md) | List possible fragments. |
| [get(rootX, rootY)](./bitburner.stanek.get.md) | Get placed fragment at location. | | [getFragment(rootX, rootY)](./bitburner.stanek.getfragment.md) | Get placed fragment at location. |
| [height()](./bitburner.stanek.height.md) | Stanek's Gift height. | | [giftHeight()](./bitburner.stanek.giftheight.md) | Stanek's Gift height. |
| [place(rootX, rootY, rotation, fragmentId)](./bitburner.stanek.place.md) | Place fragment on Stanek's Gift. | | [giftWidth()](./bitburner.stanek.giftwidth.md) | Stanek's Gift width. |
| [remove(rootX, rootY)](./bitburner.stanek.remove.md) | Remove fragment at location. | | [placeFragment(rootX, rootY, rotation, fragmentId)](./bitburner.stanek.placefragment.md) | Place fragment on Stanek's Gift. |
| [width()](./bitburner.stanek.width.md) | Stanek's Gift width. | | [removeFragment(rootX, rootY)](./bitburner.stanek.removefragment.md) | Remove fragment at location. |

@ -1,15 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. --> <!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [place](./bitburner.stanek.place.md) [Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [placeFragment](./bitburner.stanek.placefragment.md)
## Stanek.place() method ## Stanek.placeFragment() method
Place fragment on Stanek's Gift. Place fragment on Stanek's Gift.
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
place(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean; placeFragment(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
``` ```
## Parameters ## Parameters

@ -1,15 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. --> <!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [remove](./bitburner.stanek.remove.md) [Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Stanek](./bitburner.stanek.md) &gt; [removeFragment](./bitburner.stanek.removefragment.md)
## Stanek.remove() method ## Stanek.removeFragment() method
Remove fragment at location. Remove fragment at location.
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
remove(rootX: number, rootY: number): boolean; removeFragment(rootX: number, rootY: number): boolean;
``` ```
## Parameters ## Parameters

@ -32,6 +32,8 @@ export interface TIX
| [placeOrder(sym, shares, price, type, pos)](./bitburner.tix.placeorder.md) | Place order for stocks. | | [placeOrder(sym, shares, price, type, pos)](./bitburner.tix.placeorder.md) | Place order for stocks. |
| [purchase4SMarketData()](./bitburner.tix.purchase4smarketdata.md) | Purchase 4S Market Data Access. | | [purchase4SMarketData()](./bitburner.tix.purchase4smarketdata.md) | Purchase 4S Market Data Access. |
| [purchase4SMarketDataTixApi()](./bitburner.tix.purchase4smarketdatatixapi.md) | Purchase 4S Market Data TIX API Access. | | [purchase4SMarketDataTixApi()](./bitburner.tix.purchase4smarketdatatixapi.md) | Purchase 4S Market Data TIX API Access. |
| [purchaseTixApi()](./bitburner.tix.purchasetixapi.md) | Purchase TIX API Access |
| [purchaseWseAccount()](./bitburner.tix.purchasewseaccount.md) | Purchase WSE Account. |
| [sell(sym, shares)](./bitburner.tix.sell.md) | Sell stocks. | | [sell(sym, shares)](./bitburner.tix.sell.md) | Sell stocks. |
| [sellShort(sym, shares)](./bitburner.tix.sellshort.md) | Sell short stock. | | [sellShort(sym, shares)](./bitburner.tix.sellshort.md) | Sell short stock. |
| [short(sym, shares)](./bitburner.tix.short.md) | Short stocks. | | [short(sym, shares)](./bitburner.tix.short.md) | Short stocks. |

@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [TIX](./bitburner.tix.md) &gt; [purchaseTixApi](./bitburner.tix.purchasetixapi.md)
## TIX.purchaseTixApi() method
Purchase TIX API Access
<b>Signature:</b>
```typescript
purchaseTixApi(): boolean;
```
<b>Returns:</b>
boolean
True if you successfully purchased it or if you already have access, false otherwise.
## Remarks
RAM cost: 2.5 GB

@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [TIX](./bitburner.tix.md) &gt; [purchaseWseAccount](./bitburner.tix.purchasewseaccount.md)
## TIX.purchaseWseAccount() method
Purchase WSE Account.
<b>Signature:</b>
```typescript
purchaseWseAccount(): boolean;
```
<b>Returns:</b>
boolean
True if you successfully purchased it or if you already have access, false otherwise.
## Remarks
RAM cost: 2.5 GB

@ -1,7 +1,7 @@
{ {
"name": "bitburner", "name": "bitburner",
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"version": "1.5.0", "version": "1.6.0",
"main": "electron-main.js", "main": "electron-main.js",
"author": { "author": {
"name": "Daniel Xie & Olivier Gagnon" "name": "Daniel Xie & Olivier Gagnon"
@ -129,6 +129,6 @@
"electron:packager-win": "electron-packager .package bitburner --platform win32 --arch x64 --out .build --overwrite --icon .package/icon.png", "electron:packager-win": "electron-packager .package bitburner --platform win32 --arch x64 --out .build --overwrite --icon .package/icon.png",
"electron:packager-mac": "electron-packager .package bitburner --platform darwin --arch x64 --out .build --overwrite --icon .package/icon.png", "electron:packager-mac": "electron-packager .package bitburner --platform darwin --arch x64 --out .build --overwrite --icon .package/icon.png",
"electron:packager-linux": "electron-packager .package bitburner --platform linux --arch x64 --out .build --overwrite --icon .package/icon.png", "electron:packager-linux": "electron-packager .package bitburner --platform linux --arch x64 --out .build --overwrite --icon .package/icon.png",
"allbuild": "npm run build && npm run electron && git add --all && git commit -m \"allbuild commit $(which git)\" && git push -f -u origin dev" "allbuild": "npm run build && npm run electron && git add --all && git commit -m \"allbuild commit $(git rev-parse --short HEAD)\" && git push -f -u origin dev"
} }
} }

@ -17,6 +17,10 @@ import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import List from "@mui/material/List"; import List from "@mui/material/List";
import { ExpandLess, ExpandMore } from "@mui/icons-material";
import { Box, Paper, ListItemButton, ListItemText, Typography, Collapse } from "@mui/material";
import { CONSTANTS } from "../../Constants";
import { formatNumber } from "../../utils/StringHelperFunctions";
export function InstalledAugmentations(): React.ReactElement { export function InstalledAugmentations(): React.ReactElement {
const setRerender = useState(true)[1]; const setRerender = useState(true)[1];
@ -55,6 +59,38 @@ export function InstalledAugmentations(): React.ReactElement {
</Button> </Button>
</Tooltip> </Tooltip>
<List dense> <List dense>
{player.entropy > 0 &&
(() => {
const [open, setOpen] = useState(false);
return (
<Box component={Paper}>
<ListItemButton onClick={() => setOpen((old) => !old)}>
<ListItemText
primary={
<Typography color={Settings.theme.hp} style={{ whiteSpace: "pre-wrap" }}>
Entropy Virus - Level {player.entropy}
</Typography>
}
/>
{open ? (
<ExpandLess sx={{ color: Settings.theme.hp }} />
) : (
<ExpandMore sx={{ color: Settings.theme.hp }} />
)}
</ListItemButton>
<Collapse in={open} unmountOnExit>
<Box m={4}>
<Typography color={Settings.theme.hp}>
<b>All multipliers decreased by:</b>{" "}
{formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropy) * 100, 3)}% (multiplicative)
</Typography>
</Box>
</Collapse>
</Box>
);
})()}
{sourceAugs.map((e) => { {sourceAugs.map((e) => {
const aug = Augmentations[e.name]; const aug = Augmentations[e.name];

@ -425,7 +425,7 @@ BitNodes["BitNode10"] = new BitNode(
This BitNode unlocks Sleeve technology. Sleeve technology allows you to: This BitNode unlocks Sleeve technology. Sleeve technology allows you to:
<br /> <br />
<br /> <br />
1. Re-sleeve: Purchase and transfer your consciousness into a new body 1. Grafting: Visit VitaLife in New Tokyo to be able to obtain Augmentations without needing to install
<br /> <br />
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks 2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks
synchronously synchronously

@ -1466,20 +1466,19 @@ export class Bladeburner implements IBladeburner {
if (isNaN(eff) || eff < 0) { if (isNaN(eff) || eff < 0) {
throw new Error("Field Analysis Effectiveness calculated to be NaN or negative"); throw new Error("Field Analysis Effectiveness calculated to be NaN or negative");
} }
const hackingExpGain = 20 * player.hacking_exp_mult, const hackingExpGain = 20 * player.hacking_exp_mult;
charismaExpGain = 20 * player.charisma_exp_mult; const charismaExpGain = 20 * player.charisma_exp_mult;
const rankGain = 0.1 * BitNodeMultipliers.BladeburnerRank;
player.gainHackingExp(hackingExpGain); player.gainHackingExp(hackingExpGain);
player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain); player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain);
player.gainCharismaExp(charismaExpGain); player.gainCharismaExp(charismaExpGain);
this.changeRank(player, 0.1 * BitNodeMultipliers.BladeburnerRank); this.changeRank(player, rankGain);
this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate); this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate);
if (this.logging.general) { if (this.logging.general) {
this.log( this.log(
"Field analysis completed. Gained 0.1 rank, " + `Field analysis completed. Gained ${formatNumber(rankGain, 2)} rank, ` +
formatNumber(hackingExpGain, 1) + `${formatNumber(hackingExpGain, 1)} hacking exp, and ` +
" hacking exp, and " + `${formatNumber(charismaExpGain, 1)} charisma exp`,
formatNumber(charismaExpGain, 1) +
" charisma exp",
); );
} }
this.startAction(player, this.action); // Repeat action this.startAction(player, this.action); // Repeat action

@ -41,6 +41,7 @@ export const CONSTANTS: {
IntelligenceInfiltrationWeight: number; IntelligenceInfiltrationWeight: number;
IntelligenceCrimeBaseExpGain: number; IntelligenceCrimeBaseExpGain: number;
IntelligenceProgramBaseExpGain: number; IntelligenceProgramBaseExpGain: number;
IntelligenceGraftBaseExpGain: number;
IntelligenceTerminalHackBaseExpGain: number; IntelligenceTerminalHackBaseExpGain: number;
IntelligenceSingFnBaseExpGain: number; IntelligenceSingFnBaseExpGain: number;
IntelligenceClassBaseExpGain: number; IntelligenceClassBaseExpGain: number;
@ -71,6 +72,7 @@ export const CONSTANTS: {
WorkTypeCreateProgram: string; WorkTypeCreateProgram: string;
WorkTypeStudyClass: string; WorkTypeStudyClass: string;
WorkTypeCrime: string; WorkTypeCrime: string;
WorkTypeGraftAugmentation: string;
ClassStudyComputerScience: string; ClassStudyComputerScience: string;
ClassDataStructures: string; ClassDataStructures: string;
ClassNetworks: string; ClassNetworks: string;
@ -108,11 +110,14 @@ export const CONSTANTS: {
CodingContractBaseFactionRepGain: number; CodingContractBaseFactionRepGain: number;
CodingContractBaseCompanyRepGain: number; CodingContractBaseCompanyRepGain: number;
CodingContractBaseMoneyGain: number; CodingContractBaseMoneyGain: number;
AugmentationGraftingCostMult: number;
AugmentationGraftingTimeBase: number;
EntropyEffect: number;
TotalNumBitNodes: number; TotalNumBitNodes: number;
LatestUpdate: string; LatestUpdate: string;
} = { } = {
VersionString: "1.5.0", VersionString: "1.6.0",
VersionNumber: 11, VersionNumber: 12,
// Speed (in ms) at which the main loop is updated // Speed (in ms) at which the main loop is updated
_idleSpeed: 200, _idleSpeed: 200,
@ -180,6 +185,7 @@ export const CONSTANTS: {
IntelligenceInfiltrationWeight: 0.1, // Weight for how much int affects infiltration success rates IntelligenceInfiltrationWeight: 0.1, // Weight for how much int affects infiltration success rates
IntelligenceCrimeBaseExpGain: 0.05, IntelligenceCrimeBaseExpGain: 0.05,
IntelligenceProgramBaseExpGain: 0.1, // Program required hack level divided by this to determine int exp gain IntelligenceProgramBaseExpGain: 0.1, // Program required hack level divided by this to determine int exp gain
IntelligenceGraftBaseExpGain: 0.05,
IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain
IntelligenceSingFnBaseExpGain: 1.5, IntelligenceSingFnBaseExpGain: 1.5,
IntelligenceClassBaseExpGain: 0.01, IntelligenceClassBaseExpGain: 0.01,
@ -224,6 +230,7 @@ export const CONSTANTS: {
WorkTypeCreateProgram: "Working on Create a Program", WorkTypeCreateProgram: "Working on Create a Program",
WorkTypeStudyClass: "Studying or Taking a class at university", WorkTypeStudyClass: "Studying or Taking a class at university",
WorkTypeCrime: "Committing a crime", WorkTypeCrime: "Committing a crime",
WorkTypeGraftAugmentation: "Grafting an Augmentation",
ClassStudyComputerScience: "studying Computer Science", ClassStudyComputerScience: "studying Computer Science",
ClassDataStructures: "taking a Data Structures course", ClassDataStructures: "taking a Data Structures course",
@ -269,68 +276,116 @@ export const CONSTANTS: {
CodingContractBaseCompanyRepGain: 4000, CodingContractBaseCompanyRepGain: 4000,
CodingContractBaseMoneyGain: 75e6, CodingContractBaseMoneyGain: 75e6,
// Augmentation crafting multipliers
AugmentationGraftingCostMult: 3,
AugmentationGraftingTimeBase: 3600000,
// Value raised to the number of entropy stacks, then multiplied to player multipliers
EntropyEffect: 0.98,
// BitNode/Source-File related stuff // BitNode/Source-File related stuff
TotalNumBitNodes: 24, TotalNumBitNodes: 24,
LatestUpdate: ` LatestUpdate: `
v1.5.0 - Steam Cloud integration v1.6.0 - 2022-03-29 Grafting
-------------------------------- ----------------------------
** Steam Cloud Saving ** ** Vitalife secret lab **
* Added (@MartinFournier) * A new mechanic called Augmentation Grafting has been added. Resleeving has been removed.
* Credit to @nickofolas for his incredible work.
** Stanek **
* BREAKING: Many functions in the stanek API were renamed in order to avoid name collision with things like Map.prototype.get
** UI ** ** UI **
* background now matches game primary color (@nickofolas) * Major update to Sleeve, Gang UI, and Create Program (@nickofolas)
* page title contains version (@MartinFourier) * re-add pre tags to support slash n in prompt (@jacktose)
* Major text editor improvements (@nickofolas) * Tabelize linked output of 'ls' (@Master-Guy)
* Display bonus time on sleeve page (@MartinFourier) * Add the ability to filter open scripts (@phyzical)
* Several UI improvements (@nickofolas, @smolgumball, @DrCuriosity, @phyzical) * Add minHeight to editor tabs (@nickofolas)
* Fix aug display in alpha (@Dominik Winter) * Properly expand gang equipment cards to fill entire screen (@nickofolas)
* Fix display of corporation product equation (@SagePtr) * Add shortcut to Faction augmentations page from FactionsRoot (@nickofolas)
* Make Bitverse more accessible (@ChrissiQ) * Fix extra space on editor tabs (@nickofolas)
* Make corporation warehouse more accessible (@ChrissiQ) * Present offline message as list (@DSteve595)
* Make tab style more consistent (@nikfolas) * add box showing remaining augments per faction (@jjayeon)
* Add tab switching support to vim mode (@JParisFerrer)
* Show current task on gang management screen (@zeddrak)
* Fix for ui of gang members current task when set via api (@phyzical)
* Don't hide irrelevant materials if their stock is not empty and hide irrelevant divisions from Export (@SagePtr)
* Fix regex to enable alpha transparency hex codes (8 digits) (@surdaft)
** Netscript ** ** API **
* Fix bug with async. * Added dark web functions to ns api
* Add 'printf' ns function (@Ninetailed) * BREAKING: purchaseTor() should returns true if player already has Tor. (@DavidGrinberg, @waffleattack)
* Remove blob caching. * Implement getBonusTime in Corporation API (@t-wolfeadam)
* Fix formulas access check (@Ornedan) * Added functions to purchase TIX and WSI (@incubusnb)
* Fix bug in exp calculation (@qcorradi) * purchaseSleeveAug checks shock value (@incubusnb)
* Fix NaN comparison (@qcorradi) * Fix bug with hacknet api
* Fix travelToCity with bad argument (@SlyCedix) * Fix spendHashes bug
* Fix bug where augs could not be purchased via sing (@reacocard) * Added 0 cost of asleep() (@Master-Guy)
* Fix rounding error in donateToFaction (@Risenafis) * Fix some misleading corporation errors (@TheRealMaxion)
* Fix bug with weakenAnalyze (@rhobes) * expose the inBladeburner on the player object (@phyzical)
* Prevent exploit with atExit (@Ornedan) * added ram charge for stanek width and height (@phyzical)
* Double 'share' power * Fix sufficient player money check to buy back shares. (@ChrissiQ)
* Fix Static Ram Circumventing for some NS functions (@CrafterKolyan)
* added CorporationSoftCap to NetscriptDefinitions (@phyzical)
* Added definition of autocomplete() 'data' argument. (@tigercat2000)
* Adding support for text/select options in Prompt command (@PhilipArmstead)
* Added the ability to exportGame via api (@phyzical)
** Corporations ** ** Arcade **
* Fix bugs with corp API (@pigalot) * Added an arcade to New Tokyo where you can play a 4 year old version of bitburner.
* Add smart supply func to corp API (@pd)
** Misc. ** ** Misc. **
* The file API now allows GET and DELETE (@lordducky) * Add a warning triggered while auto-saves are off. (@MartinFournier)
* Force achievement calculation on BN completion (@SagePtr) * Log info for field analysis now displays actual rank gained. (@ApamNapat)
* Cleanup in repository (@MartinFourier) * Removed BladeburnerSkillCost from skill point cost description. (@ApamNapat)
* Several improvements to the electron version (@MartinFourier) * Fix handling for UpArrow in bladeburner console. (@dowinter)
* Fix bug with casino roulette (@jamie-mac) * Add GitHub action to check PRs for generated files. (@MartinFournier)
* Terminal history persists in savefile (@MartinFourier) * Cap Staneks gift at 25x25 to prevent crashes. (@waffleattack)
* Fix tests (@jamie-mac) * Remove old & unused files from repository. (@MartinFournier)
* Fix crash with electron windows tracker (@smolgumball) * Factions on the factions screens are sorted by story progress / type. (@phyzical)
* Fix BN6/7 passive reputation gain (@BrianLDev) * Fix log manager not picking up new runs of scripts. (@phyzical)
* Fix Sleeve not resetting on install (@waffleattack) * Added prettier to cicd.
* Sort joined factions (@jjayeon) * UI improvements (@phyzical)
* Update documentation / typo (@lethern, @Meowdoleon, @JohnnyUrosevic, @JosephDavidTalbot, * Documentation / Typos (@nanogyth, @Master-Guy, @incubusnb, @ApamNapat, @phyzical, @SagePtr)
@pd, @lethern, @lordducky, @zeddrak, @fearnlj01, @reasonablytall, @MatthewTh0, * Give player code a copy of Division.upgrades instead of the live object (@Ornedan)
@SagePtr, @manniL, @Jedimaster4559, @loganville, @Arrow2thekn33, @wdpk, @fwolfst, * Fix bug with small town achievement.
@fschoenfeldt, @Waladil, @AdamTReineke, @citrusmunch, @factubsio, @ashtongreen, * Fix bug with purchaseSleeveAug (@phyzical)
@ChrissiQ, @DJ-Laser, @waffleattack, @ApamNapat, @CrafterKolyan, @DSteve595) * Check before unlocking corp upgrade (@gianfun)
* General codebase improvements. (@phyzical, @Master-Guy, @ApamNapat)
* Waiting on promises in NS1 no longer freezes the script. (@Master-Guy)
* Fix bug with missing ramcost for tFormat (@TheMas3212)
* Fix crash with new prompt
* Quick fix to prevent division by 0 in terminal (@Master-Guy)
* removed ip references (@phyzical, @Master-Guy)
* Terminal now supports 'ls -l'
* Fix negative number formatting (@Master-Guy)
* Fix unique ip generation (@InDieTasten)
* remove terminal command theme from docs (@phyzical)
* Fix 'Augmentations Left' with gang factions (@nickofolas)
* Attempt to fix 'bladeburner.process()' early routing issue (@MartinFournier)
* work in progress augment fix (@phyzical)
* Fixes missing space in Smart Supply (@TheRealMaxion)
* Change license to Apache 2 with Commons Clause
* updated regex sanitization (@mbrannen)
* Sleeve fix for when faction isnt found (@phyzical)
* Fix editor "close" naming (@phyzical)
* Fix bug with sleeves where some factions would be listed as workable. (@phyzical)
* Fix research tree of product industries post-prestige (@pd)
* Added a check for exisiting industry type before expanding (@phyzical)
* fix hackAnalyzeThreads returning infinity (@chrisrabe)
* Make growthAnalyze more accurate (@dwRchyngqxs)
* Add 'Zoom -> Reset Zoom' command to Steam (@smolgumball)
* Add hasOwnProperty check to GetServer (@SagePtr)
* Speed up employee productivity calculation (@pd)
* Field Work and Security Work benefit from 'share' (@SagePtr)
* Nerf noodle bar. * Nerf noodle bar.
`, `,
}; };

@ -23,6 +23,7 @@ import { Sleeves } from "./DevMenu/ui/Sleeves";
import { Stanek } from "./DevMenu/ui/Stanek"; import { Stanek } from "./DevMenu/ui/Stanek";
import { TimeSkip } from "./DevMenu/ui/TimeSkip"; import { TimeSkip } from "./DevMenu/ui/TimeSkip";
import { Achievements } from "./DevMenu/ui/Achievements"; import { Achievements } from "./DevMenu/ui/Achievements";
import { Entropy } from "./DevMenu/ui/Entropy";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { Exploit } from "./Exploits/Exploit"; import { Exploit } from "./Exploits/Exploit";
@ -63,6 +64,7 @@ export function DevMenuRoot(props: IProps): React.ReactElement {
<TimeSkip player={props.player} engine={props.engine} /> <TimeSkip player={props.player} engine={props.engine} />
<Achievements player={props.player} engine={props.engine} /> <Achievements player={props.player} engine={props.engine} />
<Entropy player={props.player} engine={props.engine} />
</> </>
); );
} }

@ -0,0 +1,50 @@
import React from "react";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Typography from "@mui/material/Typography";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Adjuster } from "./Adjuster";
import { IEngine } from "../../IEngine";
// Update as additional BitNodes get implemented
interface IProps {
player: IPlayer;
engine: IEngine;
}
export function Entropy(props: IProps): React.ReactElement {
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>Entropy</Typography>
</AccordionSummary>
<AccordionDetails>
<Adjuster
label="Set entropy"
placeholder="entropy"
add={(num) => {
props.player.entropy += num;
props.player.applyEntropy(props.player.entropy);
}}
subtract={(num) => {
props.player.entropy -= num;
props.player.applyEntropy(props.player.entropy);
}}
tons={() => {
props.player.entropy += 1e12;
props.player.applyEntropy(props.player.entropy);
}}
reset={() => {
props.player.entropy = 0;
props.player.applyEntropy(props.player.entropy);
}}
/>
</AccordionDetails>
</Accordion>
);
}

@ -43,17 +43,21 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
function getAugs(): string[] { function getAugs(): string[] {
if (isPlayersGang) { if (isPlayersGang) {
const augs: string[] = []; let augs = Object.values(Augmentations);
for (const augName of Object.keys(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue; // Remove special augs.
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue; augs = augs.filter((a) => !a.isSpecial);
const aug = Augmentations[augName];
if (!aug.isSpecial) { if (player.bitNodeN !== 2) {
augs.push(augName); // Remove faction-unique augs outside BN2. (But keep the one for this faction.)
} augs = augs.filter((a) => a.factions.length > 1 || props.faction.augmentations.includes(a.name));
// Remove blacklisted augs.
const blacklist = [AugmentationNames.NeuroFluxGovernor, AugmentationNames.TheRedPill];
augs = augs.filter((a) => !blacklist.includes(a.name));
} }
return augs; return augs.map((a) => a.name);
} else { } else {
return props.faction.augmentations.slice(); return props.faction.augmentations.slice();
} }

@ -1,14 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { import { Box, Button, Container, Paper, TableBody, TableRow, Typography } from "@mui/material";
Box,
Button,
Container,
Paper,
TableBody,
TableRow,
Typography
} from "@mui/material";
import { Augmentations } from "../../Augmentation/Augmentations"; import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
@ -65,26 +57,28 @@ export function FactionsRoot(props: IProps): React.ReactElement {
if (isPlayersGang) { if (isPlayersGang) {
for (const augName of Object.keys(Augmentations)) { for (const augName of Object.keys(Augmentations)) {
const aug = Augmentations[augName];
if ( if (
augName === AugmentationNames.NeuroFluxGovernor || augName === AugmentationNames.NeuroFluxGovernor ||
augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2 || (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) ||
Augmentations[augName].isSpecial // Special augs (i.e. Bladeburner augs)
) continue; aug.isSpecial ||
augs.push(augName) // Exclusive augs (i.e. QLink)
(aug.factions.length <= 1 && !faction.augmentations.includes(augName) && player.bitNodeN !== 2)
)
continue;
augs.push(augName);
} }
} else { } else {
augs = faction.augmentations.slice(); augs = faction.augmentations.slice();
} }
return augs.filter( return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation)).length;
(augmentation: string) => !player.hasAugmentation(augmentation) };
).length;
}
const allFactions = Object.values(FactionNames).map(faction => faction as string) const allFactions = Object.values(FactionNames).map((faction) => faction as string);
const allJoinedFactions = props.player.factions.slice(0); const allJoinedFactions = props.player.factions.slice(0);
allJoinedFactions.sort((a, b) => allJoinedFactions.sort((a, b) => allFactions.indexOf(a) - allFactions.indexOf(b));
allFactions.indexOf(a) - allFactions.indexOf(b));
return ( return (
<Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}> <Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}>
@ -116,7 +110,7 @@ export function FactionsRoot(props: IProps): React.ReactElement {
</TableCell> </TableCell>
<TableCell align="right"> <TableCell align="right">
<Box ml={1} mb={1}> <Box ml={1} mb={1}>
<Button sx={{ width: '100%' }} onClick={() => openFactionAugPage(Factions[faction])}> <Button sx={{ width: "100%" }} onClick={() => openFactionAugPage(Factions[faction])}>
Augmentations Left: {getAugsLeft(Factions[faction], props.player)} Augmentations Left: {getAugsLeft(Factions[faction], props.player)}
</Button> </Button>
</Box> </Box>

@ -227,7 +227,6 @@ export class Gang implements IGang {
AllGangs[thisGang].territory += territoryGain; AllGangs[thisGang].territory += territoryGain;
if (AllGangs[thisGang].territory > 0.999) AllGangs[thisGang].territory = 1; if (AllGangs[thisGang].territory > 0.999) AllGangs[thisGang].territory = 1;
AllGangs[otherGang].territory -= territoryGain; AllGangs[otherGang].territory -= territoryGain;
if (AllGangs[thisGang].territory < 0.001) AllGangs[thisGang].territory = 0;
if (thisGang === gangName) { if (thisGang === gangName) {
this.clash(true); // Player won this.clash(true); // Player won
AllGangs[otherGang].power *= 1 / 1.01; AllGangs[otherGang].power *= 1 / 1.01;

@ -72,8 +72,8 @@ export function SpecialLocation(props: IProps): React.ReactElement {
/** /**
* Click handler for Resleeving button at New Tokyo VitaLife * Click handler for Resleeving button at New Tokyo VitaLife
*/ */
function handleResleeving(): void { function handleGrafting(): void {
router.toResleeves(); router.toGrafting();
} }
function renderBladeburner(): React.ReactElement { function renderBladeburner(): React.ReactElement {
@ -151,11 +151,11 @@ export function SpecialLocation(props: IProps): React.ReactElement {
); );
} }
function renderResleeving(): React.ReactElement { function renderGrafting(): React.ReactElement {
if (!player.canAccessResleeving()) { if (!player.canAccessGrafting()) {
return <></>; return <></>;
} }
return <Button onClick={handleResleeving}>Re-Sleeve</Button>; return <Button onClick={handleGrafting} sx={{ my: 5 }}>Enter the secret lab</Button>;
} }
function handleCotMG(): void { function handleCotMG(): void {
@ -299,7 +299,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
switch (props.loc.name) { switch (props.loc.name) {
case LocationName.NewTokyoVitaLife: { case LocationName.NewTokyoVitaLife: {
return renderResleeving(); return renderGrafting();
} }
case LocationName.Sector12CityHall: { case LocationName.Sector12CityHall: {
return <CreateCorporation />; return <CreateCorporation />;

@ -136,6 +136,7 @@ export const RamCosts: IMap<any> = {
kill: RamCostConstants.ScriptKillRamCost, kill: RamCostConstants.ScriptKillRamCost,
killall: RamCostConstants.ScriptKillRamCost, killall: RamCostConstants.ScriptKillRamCost,
exit: 0, exit: 0,
atExit: 0,
scp: RamCostConstants.ScriptScpRamCost, scp: RamCostConstants.ScriptScpRamCost,
ls: RamCostConstants.ScriptScanRamCost, ls: RamCostConstants.ScriptScanRamCost,
ps: RamCostConstants.ScriptScanRamCost, ps: RamCostConstants.ScriptScanRamCost,
@ -364,16 +365,16 @@ export const RamCosts: IMap<any> = {
}, },
stanek: { stanek: {
width: RamCostConstants.ScriptStanekWidth, giftWidth: RamCostConstants.ScriptStanekWidth,
height: RamCostConstants.ScriptStanekHeight, giftHeight: RamCostConstants.ScriptStanekHeight,
charge: RamCostConstants.ScriptStanekCharge, chargeFragment: RamCostConstants.ScriptStanekCharge,
fragmentDefinitions: RamCostConstants.ScriptStanekFragmentDefinitions, fragmentDefinitions: RamCostConstants.ScriptStanekFragmentDefinitions,
activeFragments: RamCostConstants.ScriptStanekPlacedFragments, activeFragments: RamCostConstants.ScriptStanekPlacedFragments,
clear: RamCostConstants.ScriptStanekClear, clearGift: RamCostConstants.ScriptStanekClear,
canPlace: RamCostConstants.ScriptStanekCanPlace, canPlaceFragment: RamCostConstants.ScriptStanekCanPlace,
place: RamCostConstants.ScriptStanekPlace, placeFragment: RamCostConstants.ScriptStanekPlace,
get: RamCostConstants.ScriptStanekFragmentAt, getFragment: RamCostConstants.ScriptStanekFragmentAt,
remove: RamCostConstants.ScriptStanekDeleteAt, removeFragment: RamCostConstants.ScriptStanekDeleteAt,
}, },
ui: { ui: {
@ -386,6 +387,12 @@ export const RamCosts: IMap<any> = {
getGameInfo: 0, getGameInfo: 0,
}, },
grafting: {
getAugmentationGraftPrice: 3.75,
getAugmentationGraftTime: 3.75,
graftAugmentation: 7.5,
},
heart: { heart: {
// Easter egg function // Easter egg function
break: 0, break: 0,

@ -70,6 +70,7 @@ import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract";
import { NetscriptCorporation } from "./NetscriptFunctions/Corporation"; import { NetscriptCorporation } from "./NetscriptFunctions/Corporation";
import { NetscriptFormulas } from "./NetscriptFunctions/Formulas"; import { NetscriptFormulas } from "./NetscriptFunctions/Formulas";
import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket"; import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket";
import { NetscriptGrafting } from "./NetscriptFunctions/Grafting";
import { IPort } from "./NetscriptPort"; import { IPort } from "./NetscriptPort";
import { import {
@ -480,10 +481,12 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const singularity = NetscriptSingularity(Player, workerScript, helper); const singularity = NetscriptSingularity(Player, workerScript, helper);
const stockmarket = NetscriptStockMarket(Player, workerScript, helper); const stockmarket = NetscriptStockMarket(Player, workerScript, helper);
const ui = NetscriptUserInterface(Player, workerScript, helper); const ui = NetscriptUserInterface(Player, workerScript, helper);
const grafting = NetscriptGrafting(Player, workerScript, helper);
const base: INS = { const base: INS = {
...singularity, ...singularity,
singularity: singularity,
gang: gang, gang: gang,
bladeburner: bladeburner, bladeburner: bladeburner,
codingcontract: codingcontract, codingcontract: codingcontract,
@ -493,6 +496,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
ui: ui, ui: ui,
formulas: formulas, formulas: formulas,
stock: stockmarket, stock: stockmarket,
grafting: grafting,
args: workerScript.args, args: workerScript.args,
hacknet: hacknet, hacknet: hacknet,
sprintf: sprintf, sprintf: sprintf,
@ -2315,6 +2319,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
tor: Player.hasTorRouter(), tor: Player.hasTorRouter(),
inBladeburner: Player.inBladeburner(), inBladeburner: Player.inBladeburner(),
hasCorporation: Player.hasCorporation(), hasCorporation: Player.hasCorporation(),
entropy: Player.entropy,
}; };
Object.assign(data.jobs, Player.jobs); Object.assign(data.jobs, Player.jobs);
return data; return data;

@ -1,5 +1,5 @@
import { FactionNames } from '../Faction/data/FactionNames'; import { FactionNames } from "../Faction/data/FactionNames";
import { GangConstants } from '../Gang/data/Constants'; import { GangConstants } from "../Gang/data/Constants";
import { INetscriptHelper } from "./INetscriptHelper"; import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator"; import { getRamCost } from "../Netscript/RamCostGenerator";
@ -48,7 +48,8 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
}; };
return { return {
createGang: function (faction: string): boolean { createGang: function (_faction: unknown): boolean {
const faction = helper.string("createGang", "faction", _faction);
helper.updateDynamicRam("createGang", getRamCost(player, "gang", "createGang")); helper.updateDynamicRam("createGang", getRamCost(player, "gang", "createGang"));
// this list is copied from Faction/ui/Root.tsx // this list is copied from Faction/ui/Root.tsx
@ -101,12 +102,13 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
return cpy; return cpy;
}, },
getMemberInformation: function (name: any): GangMemberInfo { getMemberInformation: function (_memberName: unknown): GangMemberInfo {
const memberName = helper.string("getMemberInformation", "memberName", _memberName);
helper.updateDynamicRam("getMemberInformation", getRamCost(player, "gang", "getMemberInformation")); helper.updateDynamicRam("getMemberInformation", getRamCost(player, "gang", "getMemberInformation"));
checkGangApiAccess("getMemberInformation"); checkGangApiAccess("getMemberInformation");
const gang = player.gang; const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang"); if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("getMemberInformation", name); const member = getGangMember("getMemberInformation", memberName);
return { return {
name: member.name, name: member.name,
task: member.task, task: member.task,
@ -161,16 +163,17 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
if (gang === null) throw new Error("Should not be called without Gang"); if (gang === null) throw new Error("Should not be called without Gang");
return gang.canRecruitMember(); return gang.canRecruitMember();
}, },
recruitMember: function (name: any): boolean { recruitMember: function (_memberName: unknown): boolean {
const memberName = helper.string("recruitMember", "memberName", _memberName);
helper.updateDynamicRam("recruitMember", getRamCost(player, "gang", "recruitMember")); helper.updateDynamicRam("recruitMember", getRamCost(player, "gang", "recruitMember"));
checkGangApiAccess("recruitMember"); checkGangApiAccess("recruitMember");
const gang = player.gang; const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang"); if (gang === null) throw new Error("Should not be called without Gang");
const recruited = gang.recruitMember(name); const recruited = gang.recruitMember(memberName);
if (recruited) { if (recruited) {
workerScript.log("gang.recruitMember", () => `Successfully recruited Gang Member '${name}'`); workerScript.log("gang.recruitMember", () => `Successfully recruited Gang Member '${memberName}'`);
} else { } else {
workerScript.log("gang.recruitMember", () => `Failed to recruit Gang Member '${name}'`); workerScript.log("gang.recruitMember", () => `Failed to recruit Gang Member '${memberName}'`);
} }
return recruited; return recruited;
@ -184,7 +187,9 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
tasks.unshift("Unassigned"); tasks.unshift("Unassigned");
return tasks; return tasks;
}, },
setMemberTask: function (memberName: any, taskName: any): boolean { setMemberTask: function (_memberName: unknown, _taskName: unknown): boolean {
const memberName = helper.string("setMemberTask", "memberName", _memberName);
const taskName = helper.string("setMemberTask", "taskName", _taskName);
helper.updateDynamicRam("setMemberTask", getRamCost(player, "gang", "setMemberTask")); helper.updateDynamicRam("setMemberTask", getRamCost(player, "gang", "setMemberTask"));
checkGangApiAccess("setMemberTask"); checkGangApiAccess("setMemberTask");
const member = getGangMember("setMemberTask", memberName); const member = getGangMember("setMemberTask", memberName);
@ -193,9 +198,10 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
if (!gang.getAllTaskNames().includes(taskName)) { if (!gang.getAllTaskNames().includes(taskName)) {
workerScript.log( workerScript.log(
"gang.setMemberTask", "gang.setMemberTask",
() => `Failed to assign Gang Member '${memberName}' to Invalid task '${taskName}'. '${memberName}' is now Unassigned`, () =>
`Failed to assign Gang Member '${memberName}' to Invalid task '${taskName}'. '${memberName}' is now Unassigned`,
); );
return member.assignToTask('Unassigned'); return member.assignToTask("Unassigned");
} }
const success = member.assignToTask(taskName); const success = member.assignToTask(taskName);
if (success) { if (success) {
@ -212,7 +218,8 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
return success; return success;
}, },
getTaskStats: function (taskName: any): GangTaskStats { getTaskStats: function (_taskName: unknown): GangTaskStats {
const taskName = helper.string("getTaskStats", "taskName", _taskName);
helper.updateDynamicRam("getTaskStats", getRamCost(player, "gang", "getTaskStats")); helper.updateDynamicRam("getTaskStats", getRamCost(player, "gang", "getTaskStats"));
checkGangApiAccess("getTaskStats"); checkGangApiAccess("getTaskStats");
const task = getGangTask("getTaskStats", taskName); const task = getGangTask("getTaskStats", taskName);
@ -225,7 +232,8 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
checkGangApiAccess("getEquipmentNames"); checkGangApiAccess("getEquipmentNames");
return Object.keys(GangMemberUpgrades); return Object.keys(GangMemberUpgrades);
}, },
getEquipmentCost: function (equipName: any): number { getEquipmentCost: function (_equipName: any): number {
const equipName = helper.string("getEquipmentCost", "equipName", _equipName);
helper.updateDynamicRam("getEquipmentCost", getRamCost(player, "gang", "getEquipmentCost")); helper.updateDynamicRam("getEquipmentCost", getRamCost(player, "gang", "getEquipmentCost"));
checkGangApiAccess("getEquipmentCost"); checkGangApiAccess("getEquipmentCost");
const gang = player.gang; const gang = player.gang;
@ -234,14 +242,16 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
if (upg === null) return Infinity; if (upg === null) return Infinity;
return gang.getUpgradeCost(upg); return gang.getUpgradeCost(upg);
}, },
getEquipmentType: function (equipName: any): string { getEquipmentType: function (_equipName: unknown): string {
const equipName = helper.string("getEquipmentType", "equipName", _equipName);
helper.updateDynamicRam("getEquipmentType", getRamCost(player, "gang", "getEquipmentType")); helper.updateDynamicRam("getEquipmentType", getRamCost(player, "gang", "getEquipmentType"));
checkGangApiAccess("getEquipmentType"); checkGangApiAccess("getEquipmentType");
const upg = GangMemberUpgrades[equipName]; const upg = GangMemberUpgrades[equipName];
if (upg == null) return ""; if (upg == null) return "";
return upg.getType(); return upg.getType();
}, },
getEquipmentStats: function (equipName: any): EquipmentStats { getEquipmentStats: function (_equipName: unknown): EquipmentStats {
const equipName = helper.string("getEquipmentStats", "equipName", _equipName);
helper.updateDynamicRam("getEquipmentStats", getRamCost(player, "gang", "getEquipmentStats")); helper.updateDynamicRam("getEquipmentStats", getRamCost(player, "gang", "getEquipmentStats"));
checkGangApiAccess("getEquipmentStats"); checkGangApiAccess("getEquipmentStats");
const equipment = GangMemberUpgrades[equipName]; const equipment = GangMemberUpgrades[equipName];
@ -251,7 +261,9 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
const typecheck: EquipmentStats = equipment.mults; const typecheck: EquipmentStats = equipment.mults;
return Object.assign({}, typecheck) as any; return Object.assign({}, typecheck) as any;
}, },
purchaseEquipment: function (memberName: any, equipName: any): boolean { purchaseEquipment: function (_memberName: unknown, _equipName: unknown): boolean {
const memberName = helper.string("purchaseEquipment", "memberName", _memberName);
const equipName = helper.string("purchaseEquipment", "equipName", _equipName);
helper.updateDynamicRam("purchaseEquipment", getRamCost(player, "gang", "purchaseEquipment")); helper.updateDynamicRam("purchaseEquipment", getRamCost(player, "gang", "purchaseEquipment"));
checkGangApiAccess("purchaseEquipment"); checkGangApiAccess("purchaseEquipment");
const gang = player.gang; const gang = player.gang;
@ -271,28 +283,31 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
return res; return res;
}, },
ascendMember: function (name: any): GangMemberAscension | undefined { ascendMember: function (_memberName: unknown): GangMemberAscension | undefined {
const memberName = helper.string("ascendMember", "memberName", _memberName);
helper.updateDynamicRam("ascendMember", getRamCost(player, "gang", "ascendMember")); helper.updateDynamicRam("ascendMember", getRamCost(player, "gang", "ascendMember"));
checkGangApiAccess("ascendMember"); checkGangApiAccess("ascendMember");
const gang = player.gang; const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang"); if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("ascendMember", name); const member = getGangMember("ascendMember", memberName);
if (!member.canAscend()) return; if (!member.canAscend()) return;
return gang.ascendMember(member, workerScript); return gang.ascendMember(member, workerScript);
}, },
getAscensionResult: function (name: any): GangMemberAscension | undefined { getAscensionResult: function (_memberName: unknown): GangMemberAscension | undefined {
const memberName = helper.string("getAscensionResult", "memberName", _memberName);
helper.updateDynamicRam("getAscensionResult", getRamCost(player, "gang", "getAscensionResult")); helper.updateDynamicRam("getAscensionResult", getRamCost(player, "gang", "getAscensionResult"));
checkGangApiAccess("getAscensionResult"); checkGangApiAccess("getAscensionResult");
const gang = player.gang; const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang"); if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("getAscensionResult", name); const member = getGangMember("getAscensionResult", memberName);
if (!member.canAscend()) return; if (!member.canAscend()) return;
return { return {
respect: member.earnedRespect, respect: member.earnedRespect,
...member.getAscensionResults(), ...member.getAscensionResults(),
}; };
}, },
setTerritoryWarfare: function (engage: any): void { setTerritoryWarfare: function (_engage: unknown): void {
const engage = helper.boolean(_engage);
helper.updateDynamicRam("setTerritoryWarfare", getRamCost(player, "gang", "setTerritoryWarfare")); helper.updateDynamicRam("setTerritoryWarfare", getRamCost(player, "gang", "setTerritoryWarfare"));
checkGangApiAccess("setTerritoryWarfare"); checkGangApiAccess("setTerritoryWarfare");
const gang = player.gang; const gang = player.gang;
@ -305,7 +320,8 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
workerScript.log("gang.setTerritoryWarfare", () => "Disengaging in Gang Territory Warfare"); workerScript.log("gang.setTerritoryWarfare", () => "Disengaging in Gang Territory Warfare");
} }
}, },
getChanceToWinClash: function (otherGang: any): number { getChanceToWinClash: function (_otherGang: unknown): number {
const otherGang = helper.string("getChanceToWinClash", "otherGang", _otherGang);
helper.updateDynamicRam("getChanceToWinClash", getRamCost(player, "gang", "getChanceToWinClash")); helper.updateDynamicRam("getChanceToWinClash", getRamCost(player, "gang", "getChanceToWinClash"));
checkGangApiAccess("getChanceToWinClash"); checkGangApiAccess("getChanceToWinClash");
const gang = player.gang; const gang = player.gang;

@ -0,0 +1,88 @@
import { CityName } from "../Locations/data/CityNames";
import { Augmentations } from "../Augmentation/Augmentations";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { WorkerScript } from "../Netscript/WorkerScript";
import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation";
import { getAvailableAugs } from "../PersonObjects/Grafting/ui/GraftingRoot";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Grafting as IGrafting } from "../ScriptEditor/NetscriptDefinitions";
import { Router } from "../ui/GameRoot";
import { INetscriptHelper } from "./INetscriptHelper";
export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IGrafting {
const checkGraftingAPIAccess = (func: string): void => {
if (!player.canAccessGrafting()) {
throw helper.makeRuntimeErrorMsg(
`grafting.${func}`,
"You do not currently have access to the Grafting API. This is either because you are not in BitNode 10 or because you do not have Source-File 10",
);
}
};
return {
getAugmentationGraftPrice: (_augName: unknown): number => {
const augName = helper.string("getAugmentationGraftPrice", "augName", _augName);
helper.updateDynamicRam("getAugmentationGraftPrice", getRamCost(player, "grafting", "getAugmentationGraftPrice"));
checkGraftingAPIAccess("getAugmentationGraftPrice");
if (!Augmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftPrice", `Invalid aug: ${augName}`);
}
const craftableAug = new GraftableAugmentation(Augmentations[augName]);
return craftableAug.cost;
},
getAugmentationGraftTime: (_augName: string): number => {
const augName = helper.string("getAugmentationGraftTime", "augName", _augName);
helper.updateDynamicRam("getAugmentationGraftTime", getRamCost(player, "grafting", "getAugmentationGraftTime"));
checkGraftingAPIAccess("getAugmentationGraftTime");
if (!Augmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftTime", `Invalid aug: ${augName}`);
}
const craftableAug = new GraftableAugmentation(Augmentations[augName]);
return craftableAug.time;
},
graftAugmentation: (_augName: string, _focus: unknown = true): boolean => {
const augName = helper.string("graftAugmentation", "augName", _augName);
const focus = helper.boolean(_focus);
helper.updateDynamicRam("graftAugmentation", getRamCost(player, "grafting", "graftAugmentation"));
checkGraftingAPIAccess("graftAugmentation");
if (player.city !== CityName.NewTokyo) {
throw helper.makeRuntimeErrorMsg(
"grafting.graftAugmentation",
"You must be in New Tokyo to begin crafting an Augmentation.",
);
}
if (!getAvailableAugs(player).includes(augName)) {
workerScript.log("grafting.graftAugmentation", () => `Invalid aug: ${augName}`);
return false;
}
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("graftAugmentation", () => txt);
}
const craftableAug = new GraftableAugmentation(Augmentations[augName]);
if (player.money < craftableAug.cost) {
workerScript.log("grafting.graftAugmentation", () => `You don't have enough money to craft ${augName}`);
return false;
}
player.loseMoney(craftableAug.cost, "augmentations");
player.startGraftAugmentationWork(augName, craftableAug.time);
if (focus) {
player.startFocusing();
Router.toWork();
} else if (wasFocusing) {
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("grafting.graftAugmentation", () => `Began crafting Augmentation ${augName}.`);
return true;
},
};
}

@ -14,7 +14,13 @@ import { isString } from "../utils/helpers/isString";
import { getRamCost } from "../Netscript/RamCostGenerator"; import { getRamCost } from "../Netscript/RamCostGenerator";
import { RunningScript } from "../Script/RunningScript"; import { RunningScript } from "../Script/RunningScript";
import { Singularity as ISingularity } from "../ScriptEditor/NetscriptDefinitions"; import {
AugmentationStats,
CharacterInfo,
CrimeStats,
PlayerSkills,
Singularity as ISingularity,
} from "../ScriptEditor/NetscriptDefinitions";
import { findCrime } from "../Crime/CrimeHelpers"; import { findCrime } from "../Crime/CrimeHelpers";
import { CompanyPosition } from "../Company/CompanyPosition"; import { CompanyPosition } from "../Company/CompanyPosition";
@ -49,7 +55,7 @@ export function NetscriptSingularity(
workerScript: WorkerScript, workerScript: WorkerScript,
helper: INetscriptHelper, helper: INetscriptHelper,
): ISingularity { ): ISingularity {
const getAugmentation = function (func: any, name: any): Augmentation { const getAugmentation = function (func: string, name: string): Augmentation {
if (!augmentationExists(name)) { if (!augmentationExists(name)) {
throw helper.makeRuntimeErrorMsg(func, `Invalid augmentation: '${name}'`); throw helper.makeRuntimeErrorMsg(func, `Invalid augmentation: '${name}'`);
} }
@ -57,7 +63,7 @@ export function NetscriptSingularity(
return Augmentations[name]; return Augmentations[name];
}; };
const getFaction = function (func: any, name: any): Faction { const getFaction = function (func: string, name: string): Faction {
if (!factionExists(name)) { if (!factionExists(name)) {
throw helper.makeRuntimeErrorMsg(func, `Invalid faction name: '${name}`); throw helper.makeRuntimeErrorMsg(func, `Invalid faction name: '${name}`);
} }
@ -65,7 +71,7 @@ export function NetscriptSingularity(
return Factions[name]; return Factions[name];
}; };
const getCompany = function (func: any, name: any): Company { const getCompany = function (func: string, name: string): Company {
const company = Companies[name]; const company = Companies[name];
if (company == null || !(company instanceof Company)) { if (company == null || !(company instanceof Company)) {
throw helper.makeRuntimeErrorMsg(func, `Invalid company name: '${name}'`); throw helper.makeRuntimeErrorMsg(func, `Invalid company name: '${name}'`);
@ -73,26 +79,26 @@ export function NetscriptSingularity(
return company; return company;
}; };
const runAfterReset = function (cbScript = null): void { const runAfterReset = function (cbScript: string | null = null): void {
//Run a script after reset //Run a script after reset
if (cbScript && isString(cbScript)) { if (!cbScript) return;
const home = player.getHomeComputer(); const home = player.getHomeComputer();
for (const script of home.scripts) { for (const script of home.scripts) {
if (script.filename === cbScript) { if (script.filename === cbScript) {
const ramUsage = script.ramUsage; const ramUsage = script.ramUsage;
const ramAvailable = home.maxRam - home.ramUsed; const ramAvailable = home.maxRam - home.ramUsed;
if (ramUsage > ramAvailable) { if (ramUsage > ramAvailable) {
return; // Not enough RAM return; // Not enough RAM
}
const runningScriptObj = new RunningScript(script, []); // No args
runningScriptObj.threads = 1; // Only 1 thread
startWorkerScript(player, runningScriptObj, home);
} }
const runningScriptObj = new RunningScript(script, []); // No args
runningScriptObj.threads = 1; // Only 1 thread
startWorkerScript(player, runningScriptObj, home);
} }
} }
}; };
return { return {
getOwnedAugmentations: function (purchased: any = false): any { getOwnedAugmentations: function (_purchased: unknown = false): string[] {
const purchased = helper.boolean(_purchased);
helper.updateDynamicRam("getOwnedAugmentations", getRamCost(player, "getOwnedAugmentations")); helper.updateDynamicRam("getOwnedAugmentations", getRamCost(player, "getOwnedAugmentations"));
helper.checkSingularityAccess("getOwnedAugmentations"); helper.checkSingularityAccess("getOwnedAugmentations");
const res = []; const res = [];
@ -106,82 +112,99 @@ export function NetscriptSingularity(
} }
return res; return res;
}, },
getAugmentationsFromFaction: function (facname: any): any { getAugmentationsFromFaction: function (_facName: unknown): string[] {
const facName = helper.string("getAugmentationsFromFaction", "facName", _facName);
helper.updateDynamicRam("getAugmentationsFromFaction", getRamCost(player, "getAugmentationsFromFaction")); helper.updateDynamicRam("getAugmentationsFromFaction", getRamCost(player, "getAugmentationsFromFaction"));
helper.checkSingularityAccess("getAugmentationsFromFaction"); helper.checkSingularityAccess("getAugmentationsFromFaction");
const faction = getFaction("getAugmentationsFromFaction", facname); const faction = getFaction("getAugmentationsFromFaction", facName);
// If player has a gang with this faction, return all augmentations. // If player has a gang with this faction, return all augmentations.
if (player.hasGangWith(facname)) { if (player.hasGangWith(facName)) {
const res = []; let augs = Object.values(Augmentations);
for (const augName of Object.keys(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue; // Remove special augs.
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue; augs = augs.filter((a) => !a.isSpecial);
const aug = Augmentations[augName];
if (!aug.isSpecial) { if (player.bitNodeN !== 2) {
res.push(augName); // Remove faction-unique augs outside BN2. (But keep the one for this faction.)
} augs = augs.filter((a) => a.factions.length > 1 || Factions[facName].augmentations.includes(a.name));
// Remove blacklisted augs.
const blacklist = [AugmentationNames.NeuroFluxGovernor, AugmentationNames.TheRedPill];
augs = augs.filter((a) => !blacklist.includes(a.name));
} }
return res; return augs.map((a) => a.name);
} }
return faction.augmentations.slice(); return faction.augmentations.slice();
}, },
getAugmentationCost: function (name: any): any { getAugmentationCost: function (_augName: unknown): [number, number] {
const augName = helper.string("getAugmentationCost", "augName", _augName);
helper.updateDynamicRam("getAugmentationCost", getRamCost(player, "getAugmentationCost")); helper.updateDynamicRam("getAugmentationCost", getRamCost(player, "getAugmentationCost"));
helper.checkSingularityAccess("getAugmentationCost"); helper.checkSingularityAccess("getAugmentationCost");
const aug = getAugmentation("getAugmentationCost", name); const aug = getAugmentation("getAugmentationCost", augName);
return [aug.baseRepRequirement, aug.baseCost]; return [aug.baseRepRequirement, aug.baseCost];
}, },
getAugmentationPrereq: function (name: any): any { getAugmentationPrereq: function (_augName: unknown): string[] {
const augName = helper.string("getAugmentationPrereq", "augName", _augName);
helper.updateDynamicRam("getAugmentationPrereq", getRamCost(player, "getAugmentationPrereq")); helper.updateDynamicRam("getAugmentationPrereq", getRamCost(player, "getAugmentationPrereq"));
helper.checkSingularityAccess("getAugmentationPrereq"); helper.checkSingularityAccess("getAugmentationPrereq");
const aug = getAugmentation("getAugmentationPrereq", name); const aug = getAugmentation("getAugmentationPrereq", augName);
return aug.prereqs.slice(); return aug.prereqs.slice();
}, },
getAugmentationPrice: function (name: any): any { getAugmentationPrice: function (_augName: unknown): number {
const augName = helper.string("getAugmentationPrice", "augName", _augName);
helper.updateDynamicRam("getAugmentationPrice", getRamCost(player, "getAugmentationPrice")); helper.updateDynamicRam("getAugmentationPrice", getRamCost(player, "getAugmentationPrice"));
helper.checkSingularityAccess("getAugmentationPrice"); helper.checkSingularityAccess("getAugmentationPrice");
const aug = getAugmentation("getAugmentationPrice", name); const aug = getAugmentation("getAugmentationPrice", augName);
return aug.baseCost; return aug.baseCost;
}, },
getAugmentationRepReq: function (name: any): any { getAugmentationRepReq: function (_augName: unknown): number {
const augName = helper.string("getAugmentationRepReq", "augName", _augName);
helper.updateDynamicRam("getAugmentationRepReq", getRamCost(player, "getAugmentationRepReq")); helper.updateDynamicRam("getAugmentationRepReq", getRamCost(player, "getAugmentationRepReq"));
helper.checkSingularityAccess("getAugmentationRepReq"); helper.checkSingularityAccess("getAugmentationRepReq");
const aug = getAugmentation("getAugmentationRepReq", name); const aug = getAugmentation("getAugmentationRepReq", augName);
return aug.baseRepRequirement; return aug.baseRepRequirement;
}, },
getAugmentationStats: function (name: any): any { getAugmentationStats: function (_augName: unknown): AugmentationStats {
const augName = helper.string("getAugmentationStats", "augName", _augName);
helper.updateDynamicRam("getAugmentationStats", getRamCost(player, "getAugmentationStats")); helper.updateDynamicRam("getAugmentationStats", getRamCost(player, "getAugmentationStats"));
helper.checkSingularityAccess("getAugmentationStats"); helper.checkSingularityAccess("getAugmentationStats");
const aug = getAugmentation("getAugmentationStats", name); const aug = getAugmentation("getAugmentationStats", augName);
return Object.assign({}, aug.mults); return Object.assign({}, aug.mults);
}, },
purchaseAugmentation: function (faction: any, name: any): any { purchaseAugmentation: function (_facName: unknown, _augName: unknown): boolean {
const facName = helper.string("purchaseAugmentation", "facName", _facName);
const augName = helper.string("purchaseAugmentation", "augName", _augName);
helper.updateDynamicRam("purchaseAugmentation", getRamCost(player, "purchaseAugmentation")); helper.updateDynamicRam("purchaseAugmentation", getRamCost(player, "purchaseAugmentation"));
helper.checkSingularityAccess("purchaseAugmentation"); helper.checkSingularityAccess("purchaseAugmentation");
const fac = getFaction("purchaseAugmentation", faction); const fac = getFaction("purchaseAugmentation", facName);
const aug = getAugmentation("purchaseAugmentation", name); const aug = getAugmentation("purchaseAugmentation", augName);
let augs = []; let augs = [];
if (player.hasGangWith(faction)) { if (player.hasGangWith(facName)) {
for (const augName of Object.keys(Augmentations)) { for (const augName of Object.keys(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue; const aug = Augmentations[augName];
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue; if (
const tempAug = Augmentations[augName]; augName === AugmentationNames.NeuroFluxGovernor ||
if (!tempAug.isSpecial) { (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) ||
augs.push(augName); // Special augs (i.e. Bladeburner augs)
} aug.isSpecial ||
// Exclusive augs (i.e. QLink)
(aug.factions.length <= 1 && !fac.augmentations.includes(augName) && player.bitNodeN !== 2)
)
continue;
augs.push(augName);
} }
} else { } else {
augs = fac.augmentations; augs = fac.augmentations;
} }
if (!augs.includes(name)) { if (!augs.includes(augName)) {
workerScript.log( workerScript.log(
"purchaseAugmentation", "purchaseAugmentation",
() => `Faction '${faction}' does not have the '${name}' augmentation.`, () => `Faction '${facName}' does not have the '${augName}' augmentation.`,
); );
return false; return false;
} }
@ -190,13 +213,13 @@ export function NetscriptSingularity(
if (!isNeuroflux) { if (!isNeuroflux) {
for (let j = 0; j < player.queuedAugmentations.length; ++j) { for (let j = 0; j < player.queuedAugmentations.length; ++j) {
if (player.queuedAugmentations[j].name === aug.name) { if (player.queuedAugmentations[j].name === aug.name) {
workerScript.log("purchaseAugmentation", () => `You already have the '${name}' augmentation.`); workerScript.log("purchaseAugmentation", () => `You already have the '${augName}' augmentation.`);
return false; return false;
} }
} }
for (let j = 0; j < player.augmentations.length; ++j) { for (let j = 0; j < player.augmentations.length; ++j) {
if (player.augmentations[j].name === aug.name) { if (player.augmentations[j].name === aug.name) {
workerScript.log("purchaseAugmentation", () => `You already have the '${name}' augmentation.`); workerScript.log("purchaseAugmentation", () => `You already have the '${augName}' augmentation.`);
return false; return false;
} }
} }
@ -216,7 +239,8 @@ export function NetscriptSingularity(
return false; return false;
} }
}, },
softReset: function (cbScript: any): any { softReset: function (_cbScript: unknown): void {
const cbScript = helper.string("softReset", "cbScript", _cbScript);
helper.updateDynamicRam("softReset", getRamCost(player, "softReset")); helper.updateDynamicRam("softReset", getRamCost(player, "softReset"));
helper.checkSingularityAccess("softReset"); helper.checkSingularityAccess("softReset");
@ -230,7 +254,8 @@ export function NetscriptSingularity(
workerScript.running = false; workerScript.running = false;
killWorkerScript(workerScript); killWorkerScript(workerScript);
}, },
installAugmentations: function (cbScript: any): any { installAugmentations: function (_cbScript: unknown): boolean {
const cbScript = helper.string("installAugmentations", "cbScript", _cbScript);
helper.updateDynamicRam("installAugmentations", getRamCost(player, "installAugmentations")); helper.updateDynamicRam("installAugmentations", getRamCost(player, "installAugmentations"));
helper.checkSingularityAccess("installAugmentations"); helper.checkSingularityAccess("installAugmentations");
@ -250,9 +275,11 @@ export function NetscriptSingularity(
workerScript.running = false; // Prevent workerScript from "finishing execution naturally" workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
killWorkerScript(workerScript); killWorkerScript(workerScript);
return true;
}, },
goToLocation: function (locationName: any): boolean { goToLocation: function (_locationName: unknown): boolean {
const locationName = helper.string("goToLocation", "locationName", _locationName);
helper.updateDynamicRam("goToLocation", getRamCost(player, "goToLocation")); helper.updateDynamicRam("goToLocation", getRamCost(player, "goToLocation"));
helper.checkSingularityAccess("goToLocation"); helper.checkSingularityAccess("goToLocation");
const location = Object.values(Locations).find((l) => l.name === locationName); const location = Object.values(Locations).find((l) => l.name === locationName);
@ -268,7 +295,10 @@ export function NetscriptSingularity(
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50000); player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50000);
return true; return true;
}, },
universityCourse: function (universityName: any, className: any, focus = true): any { universityCourse: function (_universityName: unknown, _className: unknown, _focus: unknown = true): boolean {
const universityName = helper.string("universityCourse", "universityName", _universityName);
const className = helper.string("universityCourse", "className", _className);
const focus = helper.boolean(_focus);
helper.updateDynamicRam("universityCourse", getRamCost(player, "universityCourse")); helper.updateDynamicRam("universityCourse", getRamCost(player, "universityCourse"));
helper.checkSingularityAccess("universityCourse"); helper.checkSingularityAccess("universityCourse");
const wasFocusing = player.focus; const wasFocusing = player.focus;
@ -356,7 +386,10 @@ export function NetscriptSingularity(
return true; return true;
}, },
gymWorkout: function (gymName: any, stat: any, focus = true): any { gymWorkout: function (_gymName: unknown, _stat: unknown, _focus: unknown = true): boolean {
const gymName = helper.string("gymWorkout", "gymName", _gymName);
const stat = helper.string("gymWorkout", "stat", _stat);
const focus = helper.boolean(_focus);
helper.updateDynamicRam("gymWorkout", getRamCost(player, "gymWorkout")); helper.updateDynamicRam("gymWorkout", getRamCost(player, "gymWorkout"));
helper.checkSingularityAccess("gymWorkout"); helper.checkSingularityAccess("gymWorkout");
const wasFocusing = player.focus; const wasFocusing = player.focus;
@ -468,11 +501,12 @@ export function NetscriptSingularity(
return true; return true;
}, },
travelToCity: function (cityname: any): any { travelToCity: function (_cityName: unknown): boolean {
const cityName = helper.string("travelToCity", "cityName", _cityName);
helper.updateDynamicRam("travelToCity", getRamCost(player, "travelToCity")); helper.updateDynamicRam("travelToCity", getRamCost(player, "travelToCity"));
helper.checkSingularityAccess("travelToCity"); helper.checkSingularityAccess("travelToCity");
switch (cityname) { switch (cityName) {
case CityName.Aevum: case CityName.Aevum:
case CityName.Chongqing: case CityName.Chongqing:
case CityName.Sector12: case CityName.Sector12:
@ -484,22 +518,22 @@ export function NetscriptSingularity(
return false; return false;
} }
player.loseMoney(CONSTANTS.TravelCost, "other"); player.loseMoney(CONSTANTS.TravelCost, "other");
player.city = cityname; player.city = cityName;
workerScript.log("travelToCity", () => `Traveled to ${cityname}`); workerScript.log("travelToCity", () => `Traveled to ${cityName}`);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50000); player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50000);
return true; return true;
default: default:
throw helper.makeRuntimeErrorMsg("travelToCity", `Invalid city name: '${cityname}'.`); throw helper.makeRuntimeErrorMsg("travelToCity", `Invalid city name: '${cityName}'.`);
} }
}, },
purchaseTor: function (): any { purchaseTor: function (): boolean {
helper.updateDynamicRam("purchaseTor", getRamCost(player, "purchaseTor")); helper.updateDynamicRam("purchaseTor", getRamCost(player, "purchaseTor"));
helper.checkSingularityAccess("purchaseTor"); helper.checkSingularityAccess("purchaseTor");
if (player.hasTorRouter()) { if (player.hasTorRouter()) {
workerScript.log("purchaseTor", () => "You already have a TOR router!"); workerScript.log("purchaseTor", () => "You already have a TOR router!");
return false; return true;
} }
if (player.money < CONSTANTS.TorRouterCost) { if (player.money < CONSTANTS.TorRouterCost) {
@ -525,7 +559,8 @@ export function NetscriptSingularity(
workerScript.log("purchaseTor", () => "You have purchased a Tor router!"); workerScript.log("purchaseTor", () => "You have purchased a Tor router!");
return true; return true;
}, },
purchaseProgram: function (programName: any): any { purchaseProgram: function (_programName: unknown): boolean {
const programName = helper.string("purchaseProgram", "programName", _programName).toLowerCase();
helper.updateDynamicRam("purchaseProgram", getRamCost(player, "purchaseProgram")); helper.updateDynamicRam("purchaseProgram", getRamCost(player, "purchaseProgram"));
helper.checkSingularityAccess("purchaseProgram"); helper.checkSingularityAccess("purchaseProgram");
@ -534,8 +569,6 @@ export function NetscriptSingularity(
return false; return false;
} }
programName = programName.toLowerCase();
const item = Object.values(DarkWebItems).find((i) => i.program.toLowerCase() === programName); const item = Object.values(DarkWebItems).find((i) => i.program.toLowerCase() === programName);
if (item == null) { if (item == null) {
workerScript.log("purchaseProgram", () => `Invalid program name: '${programName}.`); workerScript.log("purchaseProgram", () => `Invalid program name: '${programName}.`);
@ -564,12 +597,13 @@ export function NetscriptSingularity(
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 5000); player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 5000);
return true; return true;
}, },
getCurrentServer: function (): any { getCurrentServer: function (): string {
helper.updateDynamicRam("getCurrentServer", getRamCost(player, "getCurrentServer")); helper.updateDynamicRam("getCurrentServer", getRamCost(player, "getCurrentServer"));
helper.checkSingularityAccess("getCurrentServer"); helper.checkSingularityAccess("getCurrentServer");
return player.getCurrentServer().hostname; return player.getCurrentServer().hostname;
}, },
connect: function (hostname: any): any { connect: function (_hostname: unknown): boolean {
const hostname = helper.string("purchaseProgram", "hostname", _hostname);
helper.updateDynamicRam("connect", getRamCost(player, "connect")); helper.updateDynamicRam("connect", getRamCost(player, "connect"));
helper.checkSingularityAccess("connect"); helper.checkSingularityAccess("connect");
if (!hostname) { if (!hostname) {
@ -604,13 +638,13 @@ export function NetscriptSingularity(
return false; return false;
}, },
manualHack: function (): any { manualHack: function (): Promise<number> {
helper.updateDynamicRam("manualHack", getRamCost(player, "manualHack")); helper.updateDynamicRam("manualHack", getRamCost(player, "manualHack"));
helper.checkSingularityAccess("manualHack"); helper.checkSingularityAccess("manualHack");
const server = player.getCurrentServer(); const server = player.getCurrentServer();
return helper.hack(server.hostname, true); return helper.hack(server.hostname, true);
}, },
installBackdoor: function (): any { installBackdoor: function (): Promise<void> {
helper.updateDynamicRam("installBackdoor", getRamCost(player, "installBackdoor")); helper.updateDynamicRam("installBackdoor", getRamCost(player, "installBackdoor"));
helper.checkSingularityAccess("installBackdoor"); helper.checkSingularityAccess("installBackdoor");
const baseserver = player.getCurrentServer(); const baseserver = player.getCurrentServer();
@ -648,8 +682,8 @@ export function NetscriptSingularity(
helper.checkSingularityAccess("isFocused"); helper.checkSingularityAccess("isFocused");
return player.focus; return player.focus;
}, },
setFocus: function (afocus: any): boolean { setFocus: function (_focus: unknown): boolean {
const focus = helper.boolean(afocus); const focus = helper.boolean(_focus);
helper.updateDynamicRam("setFocus", getRamCost(player, "setFocus")); helper.updateDynamicRam("setFocus", getRamCost(player, "setFocus"));
helper.checkSingularityAccess("setFocus"); helper.checkSingularityAccess("setFocus");
if (!player.isWorking) { if (!player.isWorking) {
@ -677,7 +711,7 @@ export function NetscriptSingularity(
} }
return false; return false;
}, },
getStats: function (): any { getStats: function (): PlayerSkills {
helper.updateDynamicRam("getStats", getRamCost(player, "getStats")); helper.updateDynamicRam("getStats", getRamCost(player, "getStats"));
helper.checkSingularityAccess("getStats"); helper.checkSingularityAccess("getStats");
workerScript.log("getStats", () => `getStats is deprecated, please use getplayer`); workerScript.log("getStats", () => `getStats is deprecated, please use getplayer`);
@ -692,7 +726,7 @@ export function NetscriptSingularity(
intelligence: player.intelligence, intelligence: player.intelligence,
}; };
}, },
getCharacterInformation: function (): any { getCharacterInformation: function (): CharacterInfo {
helper.updateDynamicRam("getCharacterInformation", getRamCost(player, "getCharacterInformation")); helper.updateDynamicRam("getCharacterInformation", getRamCost(player, "getCharacterInformation"));
helper.checkSingularityAccess("getCharacterInformation"); helper.checkSingularityAccess("getCharacterInformation");
workerScript.log("getCharacterInformation", () => `getCharacterInformation is deprecated, please use getplayer`); workerScript.log("getCharacterInformation", () => `getCharacterInformation is deprecated, please use getplayer`);
@ -708,6 +742,8 @@ export function NetscriptSingularity(
mult: { mult: {
agility: player.agility_mult, agility: player.agility_mult,
agilityExp: player.agility_exp_mult, agilityExp: player.agility_exp_mult,
charisma: player.charisma,
charismaExp: player.charisma_exp,
companyRep: player.company_rep_mult, companyRep: player.company_rep_mult,
crimeMoney: player.crime_money_mult, crimeMoney: player.crime_money_mult,
crimeSuccess: player.crime_success_mult, crimeSuccess: player.crime_success_mult,
@ -740,21 +776,21 @@ export function NetscriptSingularity(
charismaExp: player.charisma_exp, charismaExp: player.charisma_exp,
}; };
}, },
hospitalize: function (): any { hospitalize: function (): void {
helper.updateDynamicRam("hospitalize", getRamCost(player, "hospitalize")); helper.updateDynamicRam("hospitalize", getRamCost(player, "hospitalize"));
helper.checkSingularityAccess("hospitalize"); helper.checkSingularityAccess("hospitalize");
if (player.isWorking || Router.page() === Page.Infiltration || Router.page() === Page.BitVerse) { if (player.isWorking || Router.page() === Page.Infiltration || Router.page() === Page.BitVerse) {
workerScript.log("hospitalize", () => "Cannot go to the hospital because the player is busy."); workerScript.log("hospitalize", () => "Cannot go to the hospital because the player is busy.");
return; return;
} }
return player.hospitalize(); player.hospitalize();
}, },
isBusy: function (): any { isBusy: function (): boolean {
helper.updateDynamicRam("isBusy", getRamCost(player, "isBusy")); helper.updateDynamicRam("isBusy", getRamCost(player, "isBusy"));
helper.checkSingularityAccess("isBusy"); helper.checkSingularityAccess("isBusy");
return player.isWorking || Router.page() === Page.Infiltration || Router.page() === Page.BitVerse; return player.isWorking || Router.page() === Page.Infiltration || Router.page() === Page.BitVerse;
}, },
stopAction: function (): any { stopAction: function (): boolean {
helper.updateDynamicRam("stopAction", getRamCost(player, "stopAction")); helper.updateDynamicRam("stopAction", getRamCost(player, "stopAction"));
helper.checkSingularityAccess("stopAction"); helper.checkSingularityAccess("stopAction");
if (player.isWorking) { if (player.isWorking) {
@ -768,7 +804,7 @@ export function NetscriptSingularity(
} }
return false; return false;
}, },
upgradeHomeCores: function (): any { upgradeHomeCores: function (): boolean {
helper.updateDynamicRam("upgradeHomeCores", getRamCost(player, "upgradeHomeCores")); helper.updateDynamicRam("upgradeHomeCores", getRamCost(player, "upgradeHomeCores"));
helper.checkSingularityAccess("upgradeHomeCores"); helper.checkSingularityAccess("upgradeHomeCores");
@ -798,13 +834,13 @@ export function NetscriptSingularity(
); );
return true; return true;
}, },
getUpgradeHomeCoresCost: function (): any { getUpgradeHomeCoresCost: function (): number {
helper.updateDynamicRam("getUpgradeHomeCoresCost", getRamCost(player, "getUpgradeHomeCoresCost")); helper.updateDynamicRam("getUpgradeHomeCoresCost", getRamCost(player, "getUpgradeHomeCoresCost"));
helper.checkSingularityAccess("getUpgradeHomeCoresCost"); helper.checkSingularityAccess("getUpgradeHomeCoresCost");
return player.getUpgradeHomeCoresCost(); return player.getUpgradeHomeCoresCost();
}, },
upgradeHomeRam: function (): any { upgradeHomeRam: function (): boolean {
helper.updateDynamicRam("upgradeHomeRam", getRamCost(player, "upgradeHomeRam")); helper.updateDynamicRam("upgradeHomeRam", getRamCost(player, "upgradeHomeRam"));
helper.checkSingularityAccess("upgradeHomeRam"); helper.checkSingularityAccess("upgradeHomeRam");
@ -837,13 +873,15 @@ export function NetscriptSingularity(
); );
return true; return true;
}, },
getUpgradeHomeRamCost: function (): any { getUpgradeHomeRamCost: function (): number {
helper.updateDynamicRam("getUpgradeHomeRamCost", getRamCost(player, "getUpgradeHomeRamCost")); helper.updateDynamicRam("getUpgradeHomeRamCost", getRamCost(player, "getUpgradeHomeRamCost"));
helper.checkSingularityAccess("getUpgradeHomeRamCost"); helper.checkSingularityAccess("getUpgradeHomeRamCost");
return player.getUpgradeHomeRamCost(); return player.getUpgradeHomeRamCost();
}, },
workForCompany: function (companyName: any, focus = true): any { workForCompany: function (_companyName: unknown, _focus: unknown = true): boolean {
let companyName = helper.string("workForCompany", "companyName", _companyName);
const focus = helper.boolean(_focus);
helper.updateDynamicRam("workForCompany", getRamCost(player, "workForCompany")); helper.updateDynamicRam("workForCompany", getRamCost(player, "workForCompany"));
helper.checkSingularityAccess("workForCompany"); helper.checkSingularityAccess("workForCompany");
@ -897,12 +935,14 @@ export function NetscriptSingularity(
); );
return true; return true;
}, },
applyToCompany: function (companyName: any, field: any): any { applyToCompany: function (_companyName: unknown, _field: unknown): boolean {
const companyName = helper.string("applyToCompany", "companyName", _companyName);
const field = helper.string("applyToCompany", "field", _field);
helper.updateDynamicRam("applyToCompany", getRamCost(player, "applyToCompany")); helper.updateDynamicRam("applyToCompany", getRamCost(player, "applyToCompany"));
helper.checkSingularityAccess("applyToCompany"); helper.checkSingularityAccess("applyToCompany");
getCompany("applyToCompany", companyName); getCompany("applyToCompany", companyName);
player.location = companyName; player.location = companyName as LocationName;
let res; let res;
switch (field.toLowerCase()) { switch (field.toLowerCase()) {
case "software": case "software":
@ -967,66 +1007,73 @@ export function NetscriptSingularity(
} }
return res; return res;
}, },
getCompanyRep: function (companyName: any): any { getCompanyRep: function (_companyName: unknown): number {
const companyName = helper.string("getCompanyRep", "companyName", _companyName);
helper.updateDynamicRam("getCompanyRep", getRamCost(player, "getCompanyRep")); helper.updateDynamicRam("getCompanyRep", getRamCost(player, "getCompanyRep"));
helper.checkSingularityAccess("getCompanyRep"); helper.checkSingularityAccess("getCompanyRep");
const company = getCompany("getCompanyRep", companyName); const company = getCompany("getCompanyRep", companyName);
return company.playerReputation; return company.playerReputation;
}, },
getCompanyFavor: function (companyName: any): any { getCompanyFavor: function (_companyName: unknown): number {
const companyName = helper.string("getCompanyFavor", "companyName", _companyName);
helper.updateDynamicRam("getCompanyFavor", getRamCost(player, "getCompanyFavor")); helper.updateDynamicRam("getCompanyFavor", getRamCost(player, "getCompanyFavor"));
helper.checkSingularityAccess("getCompanyFavor"); helper.checkSingularityAccess("getCompanyFavor");
const company = getCompany("getCompanyFavor", companyName); const company = getCompany("getCompanyFavor", companyName);
return company.favor; return company.favor;
}, },
getCompanyFavorGain: function (companyName: any): any { getCompanyFavorGain: function (_companyName: unknown): number {
const companyName = helper.string("getCompanyFavorGain", "companyName", _companyName);
helper.updateDynamicRam("getCompanyFavorGain", getRamCost(player, "getCompanyFavorGain")); helper.updateDynamicRam("getCompanyFavorGain", getRamCost(player, "getCompanyFavorGain"));
helper.checkSingularityAccess("getCompanyFavorGain"); helper.checkSingularityAccess("getCompanyFavorGain");
const company = getCompany("getCompanyFavorGain", companyName); const company = getCompany("getCompanyFavorGain", companyName);
return company.getFavorGain(); return company.getFavorGain();
}, },
checkFactionInvitations: function (): any { checkFactionInvitations: function (): string[] {
helper.updateDynamicRam("checkFactionInvitations", getRamCost(player, "checkFactionInvitations")); helper.updateDynamicRam("checkFactionInvitations", getRamCost(player, "checkFactionInvitations"));
helper.checkSingularityAccess("checkFactionInvitations"); helper.checkSingularityAccess("checkFactionInvitations");
// Make a copy of player.factionInvitations // Make a copy of player.factionInvitations
return player.factionInvitations.slice(); return player.factionInvitations.slice();
}, },
joinFaction: function (name: any): any { joinFaction: function (_facName: unknown): boolean {
const facName = helper.string("joinFaction", "facName", _facName);
helper.updateDynamicRam("joinFaction", getRamCost(player, "joinFaction")); helper.updateDynamicRam("joinFaction", getRamCost(player, "joinFaction"));
helper.checkSingularityAccess("joinFaction"); helper.checkSingularityAccess("joinFaction");
getFaction("joinFaction", name); getFaction("joinFaction", facName);
if (!player.factionInvitations.includes(name)) { if (!player.factionInvitations.includes(facName)) {
workerScript.log("joinFaction", () => `You have not been invited by faction '${name}'`); workerScript.log("joinFaction", () => `You have not been invited by faction '${facName}'`);
return false; return false;
} }
const fac = Factions[name]; const fac = Factions[facName];
joinFaction(fac); joinFaction(fac);
// Update Faction Invitation list to account for joined + banned factions // Update Faction Invitation list to account for joined + banned factions
for (let i = 0; i < player.factionInvitations.length; ++i) { for (let i = 0; i < player.factionInvitations.length; ++i) {
if (player.factionInvitations[i] == name || Factions[player.factionInvitations[i]].isBanned) { if (player.factionInvitations[i] == facName || Factions[player.factionInvitations[i]].isBanned) {
player.factionInvitations.splice(i, 1); player.factionInvitations.splice(i, 1);
i--; i--;
} }
} }
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 5); player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 5);
workerScript.log("joinFaction", () => `Joined the '${name}' faction.`); workerScript.log("joinFaction", () => `Joined the '${facName}' faction.`);
return true; return true;
}, },
workForFaction: function (name: any, type: any, focus = true): any { workForFaction: function (_facName: unknown, _type: unknown, _focus: unknown = true): boolean {
const facName = helper.string("workForFaction", "facName", _facName);
const type = helper.string("workForFaction", "type", _type);
const focus = helper.boolean(_focus);
helper.updateDynamicRam("workForFaction", getRamCost(player, "workForFaction")); helper.updateDynamicRam("workForFaction", getRamCost(player, "workForFaction"));
helper.checkSingularityAccess("workForFaction"); helper.checkSingularityAccess("workForFaction");
getFaction("workForFaction", name); getFaction("workForFaction", facName);
// if the player is in a gang and the target faction is any of the gang faction, fail // if the player is in a gang and the target faction is any of the gang faction, fail
if (player.inGang() && AllGangs[name] !== undefined) { if (player.inGang() && AllGangs[facName] !== undefined) {
workerScript.log("workForFaction", () => `Faction '${name}' does not offer work at the moment.`); workerScript.log("workForFaction", () => `Faction '${facName}' does not offer work at the moment.`);
return false; return false;
} }
if (!player.factions.includes(name)) { if (!player.factions.includes(facName)) {
workerScript.log("workForFaction", () => `You are not a member of '${name}'`); workerScript.log("workForFaction", () => `You are not a member of '${facName}'`);
return false; return false;
} }
@ -1036,7 +1083,7 @@ export function NetscriptSingularity(
workerScript.log("workForFaction", () => txt); workerScript.log("workForFaction", () => txt);
} }
const fac = Factions[name]; const fac = Factions[facName];
// Arrays listing factions that allow each time of work // Arrays listing factions that allow each time of work
switch (type.toLowerCase()) { switch (type.toLowerCase()) {
@ -1096,34 +1143,42 @@ export function NetscriptSingularity(
} }
return true; return true;
}, },
getFactionRep: function (name: any): any { getFactionRep: function (_facName: unknown): number {
const facName = helper.string("getFactionRep", "facName", _facName);
helper.updateDynamicRam("getFactionRep", getRamCost(player, "getFactionRep")); helper.updateDynamicRam("getFactionRep", getRamCost(player, "getFactionRep"));
helper.checkSingularityAccess("getFactionRep"); helper.checkSingularityAccess("getFactionRep");
const faction = getFaction("getFactionRep", name); const faction = getFaction("getFactionRep", facName);
return faction.playerReputation; return faction.playerReputation;
}, },
getFactionFavor: function (name: any): any { getFactionFavor: function (_facName: unknown): number {
const facName = helper.string("getFactionRep", "facName", _facName);
helper.updateDynamicRam("getFactionFavor", getRamCost(player, "getFactionFavor")); helper.updateDynamicRam("getFactionFavor", getRamCost(player, "getFactionFavor"));
helper.checkSingularityAccess("getFactionFavor"); helper.checkSingularityAccess("getFactionFavor");
const faction = getFaction("getFactionFavor", name); const faction = getFaction("getFactionFavor", facName);
return faction.favor; return faction.favor;
}, },
getFactionFavorGain: function (name: any): any { getFactionFavorGain: function (_facName: unknown): number {
const facName = helper.string("getFactionFavorGain", "facName", _facName);
helper.updateDynamicRam("getFactionFavorGain", getRamCost(player, "getFactionFavorGain")); helper.updateDynamicRam("getFactionFavorGain", getRamCost(player, "getFactionFavorGain"));
helper.checkSingularityAccess("getFactionFavorGain"); helper.checkSingularityAccess("getFactionFavorGain");
const faction = getFaction("getFactionFavorGain", name); const faction = getFaction("getFactionFavorGain", facName);
return faction.getFavorGain(); return faction.getFavorGain();
}, },
donateToFaction: function (name: any, amt: any): any { donateToFaction: function (_facName: unknown, _amt: unknown): boolean {
const facName = helper.string("donateToFaction", "facName", _facName);
const amt = helper.number("donateToFaction", "amt", _amt);
helper.updateDynamicRam("donateToFaction", getRamCost(player, "donateToFaction")); helper.updateDynamicRam("donateToFaction", getRamCost(player, "donateToFaction"));
helper.checkSingularityAccess("donateToFaction"); helper.checkSingularityAccess("donateToFaction");
const faction = getFaction("donateToFaction", name); const faction = getFaction("donateToFaction", facName);
if (!player.factions.includes(faction.name)) { if (!player.factions.includes(faction.name)) {
workerScript.log("donateToFaction", () => `You can't donate to '${name}' because you aren't a member`); workerScript.log("donateToFaction", () => `You can't donate to '${facName}' because you aren't a member`);
return false; return false;
} }
if (player.inGang() && faction.name === player.getGangFaction().name) { if (player.inGang() && faction.name === player.getGangFaction().name) {
workerScript.log("donateToFaction", () => `You can't donate to '${name}' because youre managing a gang for it`); workerScript.log(
"donateToFaction",
() => `You can't donate to '${facName}' because youre managing a gang for it`,
);
return false; return false;
} }
if (typeof amt !== "number" || amt <= 0 || isNaN(amt)) { if (typeof amt !== "number" || amt <= 0 || isNaN(amt)) {
@ -1133,7 +1188,7 @@ export function NetscriptSingularity(
if (player.money < amt) { if (player.money < amt) {
workerScript.log( workerScript.log(
"donateToFaction", "donateToFaction",
() => `You do not have enough money to donate ${numeralWrapper.formatMoney(amt)} to '${name}'`, () => `You do not have enough money to donate ${numeralWrapper.formatMoney(amt)} to '${facName}'`,
); );
return false; return false;
} }
@ -1152,13 +1207,15 @@ export function NetscriptSingularity(
workerScript.log( workerScript.log(
"donateToFaction", "donateToFaction",
() => () =>
`${numeralWrapper.formatMoney(amt)} donated to '${name}' for ${numeralWrapper.formatReputation( `${numeralWrapper.formatMoney(amt)} donated to '${facName}' for ${numeralWrapper.formatReputation(
repGain, repGain,
)} reputation`, )} reputation`,
); );
return true; return true;
}, },
createProgram: function (name: any, focus = true): any { createProgram: function (_programName: unknown, _focus: unknown = true): boolean {
const programName = helper.string("createProgram", "programName", _programName).toLowerCase();
const focus = helper.boolean(_focus);
helper.updateDynamicRam("createProgram", getRamCost(player, "createProgram")); helper.updateDynamicRam("createProgram", getRamCost(player, "createProgram"));
helper.checkSingularityAccess("createProgram"); helper.checkSingularityAccess("createProgram");
@ -1168,12 +1225,10 @@ export function NetscriptSingularity(
workerScript.log("createProgram", () => txt); workerScript.log("createProgram", () => txt);
} }
name = name.toLowerCase(); const p = Object.values(Programs).find((p) => p.name.toLowerCase() === programName);
const p = Object.values(Programs).find((p) => p.name.toLowerCase() === name);
if (p == null) { if (p == null) {
workerScript.log("createProgram", () => `The specified program does not exist: '${name}`); workerScript.log("createProgram", () => `The specified program does not exist: '${programName}`);
return false; return false;
} }
@ -1204,10 +1259,11 @@ export function NetscriptSingularity(
player.stopFocusing(); player.stopFocusing();
Router.toTerminal(); Router.toTerminal();
} }
workerScript.log("createProgram", () => `Began creating program: '${name}'`); workerScript.log("createProgram", () => `Began creating program: '${programName}'`);
return true; return true;
}, },
commitCrime: function (crimeRoughName: any): any { commitCrime: function (_crimeRoughName: unknown): number {
const crimeRoughName = helper.string("commitCrime", "crimeRoughName", _crimeRoughName);
helper.updateDynamicRam("commitCrime", getRamCost(player, "commitCrime")); helper.updateDynamicRam("commitCrime", getRamCost(player, "commitCrime"));
helper.checkSingularityAccess("commitCrime"); helper.checkSingularityAccess("commitCrime");
@ -1227,7 +1283,8 @@ export function NetscriptSingularity(
workerScript.log("commitCrime", () => `Attempting to commit ${crime.name}...`); workerScript.log("commitCrime", () => `Attempting to commit ${crime.name}...`);
return crime.commit(Router, player, 1, workerScript); return crime.commit(Router, player, 1, workerScript);
}, },
getCrimeChance: function (crimeRoughName: any): any { getCrimeChance: function (_crimeRoughName: unknown): number {
const crimeRoughName = helper.string("getCrimeChance", "crimeRoughName", _crimeRoughName);
helper.updateDynamicRam("getCrimeChance", getRamCost(player, "getCrimeChance")); helper.updateDynamicRam("getCrimeChance", getRamCost(player, "getCrimeChance"));
helper.checkSingularityAccess("getCrimeChance"); helper.checkSingularityAccess("getCrimeChance");
@ -1238,7 +1295,8 @@ export function NetscriptSingularity(
return crime.successRate(player); return crime.successRate(player);
}, },
getCrimeStats: function (crimeRoughName: any): any { getCrimeStats: function (_crimeRoughName: unknown): CrimeStats {
const crimeRoughName = helper.string("getCrimeStats", "crimeRoughName", _crimeRoughName);
helper.updateDynamicRam("getCrimeStats", getRamCost(player, "getCrimeStats")); helper.updateDynamicRam("getCrimeStats", getRamCost(player, "getCrimeStats"));
helper.checkSingularityAccess("getCrimeStats"); helper.checkSingularityAccess("getCrimeStats");
@ -1260,7 +1318,8 @@ export function NetscriptSingularity(
} }
return Object.values(DarkWebItems).map((p) => p.program); return Object.values(DarkWebItems).map((p) => p.program);
}, },
getDarkwebProgramCost: function (programName: any): any { getDarkwebProgramCost: function (_programName: unknown): number {
const programName = helper.string("getDarkwebProgramCost", "programName", _programName).toLowerCase();
helper.updateDynamicRam("getDarkwebProgramCost", getRamCost(player, "getDarkwebProgramCost")); helper.updateDynamicRam("getDarkwebProgramCost", getRamCost(player, "getDarkwebProgramCost"));
helper.checkSingularityAccess("getDarkwebProgramCost"); helper.checkSingularityAccess("getDarkwebProgramCost");
@ -1272,7 +1331,6 @@ export function NetscriptSingularity(
return -1; return -1;
} }
programName = programName.toLowerCase();
const item = Object.values(DarkWebItems).find((i) => i.program.toLowerCase() === programName); const item = Object.values(DarkWebItems).find((i) => i.program.toLowerCase() === programName);
// If the program doesn't exist, throw an error. The reasoning here is that the 99% case is that // If the program doesn't exist, throw an error. The reasoning here is that the 99% case is that

@ -10,10 +10,16 @@ import { Augmentations } from "../Augmentation/Augmentations";
import { CityName } from "../Locations/data/CityNames"; import { CityName } from "../Locations/data/CityNames";
import { findCrime } from "../Crime/CrimeHelpers"; import { findCrime } from "../Crime/CrimeHelpers";
import { Sleeve as ISleeve } from "../ScriptEditor/NetscriptDefinitions"; import {
AugmentPair,
Sleeve as ISleeve,
SleeveInformation,
SleeveSkills,
SleeveTask,
} from "../ScriptEditor/NetscriptDefinitions";
export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): ISleeve { export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): ISleeve {
const checkSleeveAPIAccess = function (func: any): void { const checkSleeveAPIAccess = function (func: string): void {
if (player.bitNodeN !== 10 && !SourceFileFlags[10]) { if (player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw helper.makeRuntimeErrorMsg( throw helper.makeRuntimeErrorMsg(
`sleeve.${func}`, `sleeve.${func}`,
@ -22,7 +28,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
} }
}; };
const checkSleeveNumber = function (func: any, sleeveNumber: any): void { const checkSleeveNumber = function (func: string, sleeveNumber: number): void {
if (sleeveNumber >= player.sleeves.length || sleeveNumber < 0) { if (sleeveNumber >= player.sleeves.length || sleeveNumber < 0) {
const msg = `Invalid sleeve number: ${sleeveNumber}`; const msg = `Invalid sleeve number: ${sleeveNumber}`;
workerScript.log(func, () => msg); workerScript.log(func, () => msg);
@ -30,7 +36,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
} }
}; };
const getSleeveStats = function (sleeveNumber: any): any { const getSleeveStats = function (sleeveNumber: number): SleeveSkills {
const sl = player.sleeves[sleeveNumber]; const sl = player.sleeves[sleeveNumber];
return { return {
shock: 100 - sl.shock, shock: 100 - sl.shock,
@ -42,7 +48,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
agility: sl.agility, agility: sl.agility,
charisma: sl.charisma, charisma: sl.charisma,
}; };
} };
return { return {
getNumSleeves: function (): number { getNumSleeves: function (): number {
@ -50,23 +56,23 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
checkSleeveAPIAccess("getNumSleeves"); checkSleeveAPIAccess("getNumSleeves");
return player.sleeves.length; return player.sleeves.length;
}, },
setToShockRecovery: function (asleeveNumber: any = 0): boolean { setToShockRecovery: function (_sleeveNumber: unknown): boolean {
const sleeveNumber = helper.number("setToShockRecovery", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("setToShockRecovery", "sleeveNumber", _sleeveNumber);
helper.updateDynamicRam("setToShockRecovery", getRamCost(player, "sleeve", "setToShockRecovery")); helper.updateDynamicRam("setToShockRecovery", getRamCost(player, "sleeve", "setToShockRecovery"));
checkSleeveAPIAccess("setToShockRecovery"); checkSleeveAPIAccess("setToShockRecovery");
checkSleeveNumber("setToShockRecovery", sleeveNumber); checkSleeveNumber("setToShockRecovery", sleeveNumber);
return player.sleeves[sleeveNumber].shockRecovery(player); return player.sleeves[sleeveNumber].shockRecovery(player);
}, },
setToSynchronize: function (asleeveNumber: any = 0): boolean { setToSynchronize: function (_sleeveNumber: unknown): boolean {
const sleeveNumber = helper.number("setToSynchronize", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("setToSynchronize", "sleeveNumber", _sleeveNumber);
helper.updateDynamicRam("setToSynchronize", getRamCost(player, "sleeve", "setToSynchronize")); helper.updateDynamicRam("setToSynchronize", getRamCost(player, "sleeve", "setToSynchronize"));
checkSleeveAPIAccess("setToSynchronize"); checkSleeveAPIAccess("setToSynchronize");
checkSleeveNumber("setToSynchronize", sleeveNumber); checkSleeveNumber("setToSynchronize", sleeveNumber);
return player.sleeves[sleeveNumber].synchronize(player); return player.sleeves[sleeveNumber].synchronize(player);
}, },
setToCommitCrime: function (asleeveNumber: any = 0, aCrimeRoughName: any = ""): boolean { setToCommitCrime: function (_sleeveNumber: unknown, _crimeRoughName: unknown): boolean {
const sleeveNumber = helper.number("setToCommitCrime", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("setToCommitCrime", "sleeveNumber", _sleeveNumber);
const crimeRoughName = helper.string("setToCommitCrime", "crimeName", aCrimeRoughName); const crimeRoughName = helper.string("setToCommitCrime", "crimeName", _crimeRoughName);
helper.updateDynamicRam("setToCommitCrime", getRamCost(player, "sleeve", "setToCommitCrime")); helper.updateDynamicRam("setToCommitCrime", getRamCost(player, "sleeve", "setToCommitCrime"));
checkSleeveAPIAccess("setToCommitCrime"); checkSleeveAPIAccess("setToCommitCrime");
checkSleeveNumber("setToCommitCrime", sleeveNumber); checkSleeveNumber("setToCommitCrime", sleeveNumber);
@ -76,25 +82,25 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
} }
return player.sleeves[sleeveNumber].commitCrime(player, crime.name); return player.sleeves[sleeveNumber].commitCrime(player, crime.name);
}, },
setToUniversityCourse: function (asleeveNumber: any = 0, auniversityName: any = "", aclassName: any = ""): boolean { setToUniversityCourse: function (_sleeveNumber: unknown, _universityName: unknown, _className: unknown): boolean {
const sleeveNumber = helper.number("setToUniversityCourse", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("setToUniversityCourse", "sleeveNumber", _sleeveNumber);
const universityName = helper.string("setToUniversityCourse", "universityName", auniversityName); const universityName = helper.string("setToUniversityCourse", "universityName", _universityName);
const className = helper.string("setToUniversityCourse", "className", aclassName); const className = helper.string("setToUniversityCourse", "className", _className);
helper.updateDynamicRam("setToUniversityCourse", getRamCost(player, "sleeve", "setToUniversityCourse")); helper.updateDynamicRam("setToUniversityCourse", getRamCost(player, "sleeve", "setToUniversityCourse"));
checkSleeveAPIAccess("setToUniversityCourse"); checkSleeveAPIAccess("setToUniversityCourse");
checkSleeveNumber("setToUniversityCourse", sleeveNumber); checkSleeveNumber("setToUniversityCourse", sleeveNumber);
return player.sleeves[sleeveNumber].takeUniversityCourse(player, universityName, className); return player.sleeves[sleeveNumber].takeUniversityCourse(player, universityName, className);
}, },
travel: function (asleeveNumber: any = 0, acityName: any = ""): boolean { travel: function (_sleeveNumber: unknown, _cityName: unknown): boolean {
const sleeveNumber = helper.number("travel", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("travel", "sleeveNumber", _sleeveNumber);
const cityName = helper.string("setToUniversityCourse", "cityName", acityName); const cityName = helper.string("setToUniversityCourse", "cityName", _cityName);
helper.updateDynamicRam("travel", getRamCost(player, "sleeve", "travel")); helper.updateDynamicRam("travel", getRamCost(player, "sleeve", "travel"));
checkSleeveAPIAccess("travel"); checkSleeveAPIAccess("travel");
checkSleeveNumber("travel", sleeveNumber); checkSleeveNumber("travel", sleeveNumber);
return player.sleeves[sleeveNumber].travel(player, cityName as CityName); return player.sleeves[sleeveNumber].travel(player, cityName as CityName);
}, },
setToCompanyWork: function (asleeveNumber: any = 0, acompanyName: any = ""): boolean { setToCompanyWork: function (_sleeveNumber: unknown, acompanyName: unknown): boolean {
const sleeveNumber = helper.number("setToCompanyWork", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("setToCompanyWork", "sleeveNumber", _sleeveNumber);
const companyName = helper.string("setToUniversityCourse", "companyName", acompanyName); const companyName = helper.string("setToUniversityCourse", "companyName", acompanyName);
helper.updateDynamicRam("setToCompanyWork", getRamCost(player, "sleeve", "setToCompanyWork")); helper.updateDynamicRam("setToCompanyWork", getRamCost(player, "sleeve", "setToCompanyWork"));
checkSleeveAPIAccess("setToCompanyWork"); checkSleeveAPIAccess("setToCompanyWork");
@ -116,10 +122,10 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
return player.sleeves[sleeveNumber].workForCompany(player, companyName); return player.sleeves[sleeveNumber].workForCompany(player, companyName);
}, },
setToFactionWork: function (asleeveNumber: any = 0, afactionName: any = "", aworkType: any = ""): boolean { setToFactionWork: function (_sleeveNumber: unknown, _factionName: unknown, _workType: unknown): boolean {
const sleeveNumber = helper.number("setToFactionWork", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("setToFactionWork", "sleeveNumber", _sleeveNumber);
const factionName = helper.string("setToUniversityCourse", "factionName", afactionName); const factionName = helper.string("setToUniversityCourse", "factionName", _factionName);
const workType = helper.string("setToUniversityCourse", "workType", aworkType); const workType = helper.string("setToUniversityCourse", "workType", _workType);
helper.updateDynamicRam("setToFactionWork", getRamCost(player, "sleeve", "setToFactionWork")); helper.updateDynamicRam("setToFactionWork", getRamCost(player, "sleeve", "setToFactionWork"));
checkSleeveAPIAccess("setToFactionWork"); checkSleeveAPIAccess("setToFactionWork");
checkSleeveNumber("setToFactionWork", sleeveNumber); checkSleeveNumber("setToFactionWork", sleeveNumber);
@ -140,40 +146,25 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
return player.sleeves[sleeveNumber].workForFaction(player, factionName, workType); return player.sleeves[sleeveNumber].workForFaction(player, factionName, workType);
}, },
setToGymWorkout: function (asleeveNumber: any = 0, agymName: any = "", astat: any = ""): boolean { setToGymWorkout: function (_sleeveNumber: unknown, _gymName: unknown, _stat: unknown): boolean {
const sleeveNumber = helper.number("setToGymWorkout", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("setToGymWorkout", "sleeveNumber", _sleeveNumber);
const gymName = helper.string("setToUniversityCourse", "gymName", agymName); const gymName = helper.string("setToUniversityCourse", "gymName", _gymName);
const stat = helper.string("setToUniversityCourse", "stat", astat); const stat = helper.string("setToUniversityCourse", "stat", _stat);
helper.updateDynamicRam("setToGymWorkout", getRamCost(player, "sleeve", "setToGymWorkout")); helper.updateDynamicRam("setToGymWorkout", getRamCost(player, "sleeve", "setToGymWorkout"));
checkSleeveAPIAccess("setToGymWorkout"); checkSleeveAPIAccess("setToGymWorkout");
checkSleeveNumber("setToGymWorkout", sleeveNumber); checkSleeveNumber("setToGymWorkout", sleeveNumber);
return player.sleeves[sleeveNumber].workoutAtGym(player, gymName, stat); return player.sleeves[sleeveNumber].workoutAtGym(player, gymName, stat);
}, },
getSleeveStats: function (asleeveNumber: any = 0): { getSleeveStats: function (_sleeveNumber: unknown): SleeveSkills {
shock: number; const sleeveNumber = helper.number("getSleeveStats", "sleeveNumber", _sleeveNumber);
sync: number;
hacking: number;
strength: number;
defense: number;
dexterity: number;
agility: number;
charisma: number;
} {
const sleeveNumber = helper.number("getSleeveStats", "sleeveNumber", asleeveNumber);
helper.updateDynamicRam("getSleeveStats", getRamCost(player, "sleeve", "getSleeveStats")); helper.updateDynamicRam("getSleeveStats", getRamCost(player, "sleeve", "getSleeveStats"));
checkSleeveAPIAccess("getSleeveStats"); checkSleeveAPIAccess("getSleeveStats");
checkSleeveNumber("getSleeveStats", sleeveNumber); checkSleeveNumber("getSleeveStats", sleeveNumber);
return getSleeveStats(sleeveNumber) return getSleeveStats(sleeveNumber);
}, },
getTask: function (asleeveNumber: any = 0): { getTask: function (_sleeveNumber: unknown): SleeveTask {
task: string; const sleeveNumber = helper.number("getTask", "sleeveNumber", _sleeveNumber);
crime: string;
location: string;
gymStatType: string;
factionWorkType: string;
} {
const sleeveNumber = helper.number("getTask", "sleeveNumber", asleeveNumber);
helper.updateDynamicRam("getTask", getRamCost(player, "sleeve", "getTask")); helper.updateDynamicRam("getTask", getRamCost(player, "sleeve", "getTask"));
checkSleeveAPIAccess("getTask"); checkSleeveAPIAccess("getTask");
checkSleeveNumber("getTask", sleeveNumber); checkSleeveNumber("getTask", sleeveNumber);
@ -187,14 +178,15 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
factionWorkType: FactionWorkType[sl.factionWorkType], factionWorkType: FactionWorkType[sl.factionWorkType],
}; };
}, },
getInformation: function (asleeveNumber: any = 0): any { getInformation: function (_sleeveNumber: unknown): SleeveInformation {
const sleeveNumber = helper.number("getInformation", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("getInformation", "sleeveNumber", _sleeveNumber);
helper.updateDynamicRam("getInformation", getRamCost(player, "sleeve", "getInformation")); helper.updateDynamicRam("getInformation", getRamCost(player, "sleeve", "getInformation"));
checkSleeveAPIAccess("getInformation"); checkSleeveAPIAccess("getInformation");
checkSleeveNumber("getInformation", sleeveNumber); checkSleeveNumber("getInformation", sleeveNumber);
const sl = player.sleeves[sleeveNumber]; const sl = player.sleeves[sleeveNumber];
return { return {
tor: false,
city: sl.city, city: sl.city,
hp: sl.hp, hp: sl.hp,
jobs: Object.keys(player.jobs), // technically sleeves have the same jobs as the player. jobs: Object.keys(player.jobs), // technically sleeves have the same jobs as the player.
@ -252,8 +244,8 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
workRepGain: sl.getRepGain(player), workRepGain: sl.getRepGain(player),
}; };
}, },
getSleeveAugmentations: function (asleeveNumber: any = 0): string[] { getSleeveAugmentations: function (_sleeveNumber: unknown): string[] {
const sleeveNumber = helper.number("getSleeveAugmentations", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("getSleeveAugmentations", "sleeveNumber", _sleeveNumber);
helper.updateDynamicRam("getSleeveAugmentations", getRamCost(player, "sleeve", "getSleeveAugmentations")); helper.updateDynamicRam("getSleeveAugmentations", getRamCost(player, "sleeve", "getSleeveAugmentations"));
checkSleeveAPIAccess("getSleeveAugmentations"); checkSleeveAPIAccess("getSleeveAugmentations");
checkSleeveNumber("getSleeveAugmentations", sleeveNumber); checkSleeveNumber("getSleeveAugmentations", sleeveNumber);
@ -264,11 +256,8 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
} }
return augs; return augs;
}, },
getSleevePurchasableAugs: function (asleeveNumber: any = 0): { getSleevePurchasableAugs: function (_sleeveNumber: unknown): AugmentPair[] {
name: string; const sleeveNumber = helper.number("getSleevePurchasableAugs", "sleeveNumber", _sleeveNumber);
cost: number;
}[] {
const sleeveNumber = helper.number("getSleevePurchasableAugs", "sleeveNumber", asleeveNumber);
helper.updateDynamicRam("getSleevePurchasableAugs", getRamCost(player, "sleeve", "getSleevePurchasableAugs")); helper.updateDynamicRam("getSleevePurchasableAugs", getRamCost(player, "sleeve", "getSleevePurchasableAugs"));
checkSleeveAPIAccess("getSleevePurchasableAugs"); checkSleeveAPIAccess("getSleevePurchasableAugs");
checkSleeveNumber("getSleevePurchasableAugs", sleeveNumber); checkSleeveNumber("getSleevePurchasableAugs", sleeveNumber);
@ -285,9 +274,9 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
return augs; return augs;
}, },
purchaseSleeveAug: function (asleeveNumber: any = 0, aaugName: any = ""): boolean { purchaseSleeveAug: function (_sleeveNumber: unknown, _augName: unknown): boolean {
const sleeveNumber = helper.number("purchaseSleeveAug", "sleeveNumber", asleeveNumber); const sleeveNumber = helper.number("purchaseSleeveAug", "sleeveNumber", _sleeveNumber);
const augName = helper.string("purchaseSleeveAug", "augName", aaugName); const augName = helper.string("purchaseSleeveAug", "augName", _augName);
helper.updateDynamicRam("purchaseSleeveAug", getRamCost(player, "sleeve", "purchaseSleeveAug")); helper.updateDynamicRam("purchaseSleeveAug", getRamCost(player, "sleeve", "purchaseSleeveAug"));
checkSleeveAPIAccess("purchaseSleeveAug"); checkSleeveAPIAccess("purchaseSleeveAug");
checkSleeveNumber("purchaseSleeveAug", sleeveNumber); checkSleeveNumber("purchaseSleeveAug", sleeveNumber);

@ -22,28 +22,29 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
} }
return { return {
width: function (): number { giftWidth: function (): number {
helper.updateDynamicRam("width", getRamCost(player, "stanek", "width")); helper.updateDynamicRam("giftWidth", getRamCost(player, "stanek", "giftWidth"));
checkStanekAPIAccess("width"); checkStanekAPIAccess("giftWidth");
return staneksGift.width(); return staneksGift.width();
}, },
height: function (): number { giftHeight: function (): number {
helper.updateDynamicRam("height", getRamCost(player, "stanek", "height")); helper.updateDynamicRam("giftHeight", getRamCost(player, "stanek", "giftHeight"));
checkStanekAPIAccess("height"); checkStanekAPIAccess("giftHeight");
return staneksGift.height(); return staneksGift.height();
}, },
charge: function (_rootX: unknown, _rootY: unknown): Promise<void> { chargeFragment: function (_rootX: unknown, _rootY: unknown): Promise<void> {
const rootX = helper.number("stanek.charge", "rootX", _rootX); const rootX = helper.number("stanek.chargeFragment", "rootX", _rootX);
const rootY = helper.number("stanek.charge", "rootY", _rootY); const rootY = helper.number("stanek.chargeFragment", "rootY", _rootY);
helper.updateDynamicRam("charge", getRamCost(player, "stanek", "charge")); helper.updateDynamicRam("chargeFragment", getRamCost(player, "stanek", "chargeFragment"));
checkStanekAPIAccess("charge"); checkStanekAPIAccess("chargeFragment");
const fragment = staneksGift.findFragment(rootX, rootY); const fragment = staneksGift.findFragment(rootX, rootY);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.charge", `No fragment with root (${rootX}, ${rootY}).`); if (!fragment)
throw helper.makeRuntimeErrorMsg("stanek.chargeFragment", `No fragment with root (${rootX}, ${rootY}).`);
const time = staneksGift.inBonus() ? 200 : 1000; const time = staneksGift.inBonus() ? 200 : 1000;
return netscriptDelay(time, workerScript).then(function () { return netscriptDelay(time, workerScript).then(function () {
const charge = staneksGift.charge(player, fragment, workerScript.scriptRef.threads); const charge = staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
workerScript.log("stanek.charge", () => `Charged fragment for ${charge} charge.`); workerScript.log("stanek.chargeFragment", () => `Charged fragment for ${charge} charge.`);
return Promise.resolve(); return Promise.resolve();
}); });
}, },
@ -61,49 +62,49 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
return { ...af.copy(), ...af.fragment().copy() }; return { ...af.copy(), ...af.fragment().copy() };
}); });
}, },
clear: function (): void { clearGift: function (): void {
helper.updateDynamicRam("clear", getRamCost(player, "stanek", "clear")); helper.updateDynamicRam("clearGift", getRamCost(player, "stanek", "clearGift"));
checkStanekAPIAccess("clear"); checkStanekAPIAccess("clearGift");
workerScript.log("stanek.clear", () => `Cleared Stanek's Gift.`); workerScript.log("stanek.clearGift", () => `Cleared Stanek's Gift.`);
staneksGift.clear(); staneksGift.clear();
}, },
canPlace: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean { canPlaceFragment: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
const rootX = helper.number("stanek.canPlace", "rootX", _rootX); const rootX = helper.number("stanek.canPlaceFragment", "rootX", _rootX);
const rootY = helper.number("stanek.canPlace", "rootY", _rootY); const rootY = helper.number("stanek.canPlaceFragment", "rootY", _rootY);
const rotation = helper.number("stanek.canPlace", "rotation", _rotation); const rotation = helper.number("stanek.canPlaceFragment", "rotation", _rotation);
const fragmentId = helper.number("stanek.canPlace", "fragmentId", _fragmentId); const fragmentId = helper.number("stanek.canPlaceFragment", "fragmentId", _fragmentId);
helper.updateDynamicRam("canPlace", getRamCost(player, "stanek", "canPlace")); helper.updateDynamicRam("canPlaceFragment", getRamCost(player, "stanek", "canPlaceFragment"));
checkStanekAPIAccess("canPlace"); checkStanekAPIAccess("canPlaceFragment");
const fragment = FragmentById(fragmentId); const fragment = FragmentById(fragmentId);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.canPlace", `Invalid fragment id: ${fragmentId}`); if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.canPlaceFragment", `Invalid fragment id: ${fragmentId}`);
const can = staneksGift.canPlace(rootX, rootY, rotation, fragment); const can = staneksGift.canPlace(rootX, rootY, rotation, fragment);
return can; return can;
}, },
place: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean { placeFragment: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
const rootX = helper.number("stanek.place", "rootX", _rootX); const rootX = helper.number("stanek.placeFragment", "rootX", _rootX);
const rootY = helper.number("stanek.place", "rootY", _rootY); const rootY = helper.number("stanek.placeFragment", "rootY", _rootY);
const rotation = helper.number("stanek.place", "rotation", _rotation); const rotation = helper.number("stanek.placeFragment", "rotation", _rotation);
const fragmentId = helper.number("stanek.place", "fragmentId", _fragmentId); const fragmentId = helper.number("stanek.placeFragment", "fragmentId", _fragmentId);
helper.updateDynamicRam("place", getRamCost(player, "stanek", "place")); helper.updateDynamicRam("placeFragment", getRamCost(player, "stanek", "placeFragment"));
checkStanekAPIAccess("place"); checkStanekAPIAccess("placeFragment");
const fragment = FragmentById(fragmentId); const fragment = FragmentById(fragmentId);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.place", `Invalid fragment id: ${fragmentId}`); if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.placeFragment", `Invalid fragment id: ${fragmentId}`);
return staneksGift.place(rootX, rootY, rotation, fragment); return staneksGift.place(rootX, rootY, rotation, fragment);
}, },
get: function (_rootX: unknown, _rootY: unknown): IActiveFragment | undefined { getFragment: function (_rootX: unknown, _rootY: unknown): IActiveFragment | undefined {
const rootX = helper.number("stanek.get", "rootX", _rootX); const rootX = helper.number("stanek.getFragment", "rootX", _rootX);
const rootY = helper.number("stanek.get", "rootY", _rootY); const rootY = helper.number("stanek.getFragment", "rootY", _rootY);
helper.updateDynamicRam("get", getRamCost(player, "stanek", "get")); helper.updateDynamicRam("getFragment", getRamCost(player, "stanek", "getFragment"));
checkStanekAPIAccess("get"); checkStanekAPIAccess("getFragment");
const fragment = staneksGift.findFragment(rootX, rootY); const fragment = staneksGift.findFragment(rootX, rootY);
if (fragment !== undefined) return fragment.copy(); if (fragment !== undefined) return fragment.copy();
return undefined; return undefined;
}, },
remove: function (_rootX: unknown, _rootY: unknown): boolean { removeFragment: function (_rootX: unknown, _rootY: unknown): boolean {
const rootX = helper.number("stanek.remove", "rootX", _rootX); const rootX = helper.number("stanek.removeFragment", "rootX", _rootX);
const rootY = helper.number("stanek.remove", "rootY", _rootY); const rootY = helper.number("stanek.removeFragment", "rootY", _rootY);
helper.updateDynamicRam("remove", getRamCost(player, "stanek", "remove")); helper.updateDynamicRam("removeFragment", getRamCost(player, "stanek", "removeFragment"));
checkStanekAPIAccess("remove"); checkStanekAPIAccess("removeFragment");
return staneksGift.delete(rootX, rootY); return staneksGift.delete(rootX, rootY);
}, },
}; };

@ -0,0 +1,52 @@
import { IMap } from "../../types";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../IPlayer";
export const calculateEntropy = (player: IPlayer, stacks = 1): IMap<number> => {
const multipliers: IMap<number> = {
hacking_chance_mult: player.hacking_chance_mult,
hacking_speed_mult: player.hacking_speed_mult,
hacking_money_mult: player.hacking_money_mult,
hacking_grow_mult: player.hacking_grow_mult,
hacking_mult: player.hacking_mult,
strength_mult: player.strength_mult,
defense_mult: player.defense_mult,
dexterity_mult: player.dexterity_mult,
agility_mult: player.agility_mult,
charisma_mult: player.charisma_mult,
hacking_exp_mult: player.hacking_exp_mult,
strength_exp_mult: player.strength_exp_mult,
defense_exp_mult: player.defense_exp_mult,
dexterity_exp_mult: player.dexterity_exp_mult,
agility_exp_mult: player.agility_exp_mult,
charisma_exp_mult: player.charisma_exp_mult,
company_rep_mult: player.company_rep_mult,
faction_rep_mult: player.faction_rep_mult,
crime_money_mult: player.crime_money_mult,
crime_success_mult: player.crime_success_mult,
hacknet_node_money_mult: player.hacknet_node_money_mult,
hacknet_node_purchase_cost_mult: player.hacknet_node_purchase_cost_mult,
hacknet_node_ram_cost_mult: player.hacknet_node_ram_cost_mult,
hacknet_node_core_cost_mult: player.hacknet_node_core_cost_mult,
hacknet_node_level_cost_mult: player.hacknet_node_level_cost_mult,
work_money_mult: player.work_money_mult,
bladeburner_max_stamina_mult: player.bladeburner_max_stamina_mult,
bladeburner_stamina_gain_mult: player.bladeburner_stamina_gain_mult,
bladeburner_analysis_mult: player.bladeburner_analysis_mult,
bladeburner_success_chance_mult: player.bladeburner_success_chance_mult,
};
for (const [mult, val] of Object.entries(multipliers)) {
multipliers[mult] = val * CONSTANTS.EntropyEffect ** stacks;
}
return multipliers;
};

@ -0,0 +1,31 @@
import { sum } from "lodash";
import { Augmentation } from "../../Augmentation/Augmentation";
import { CONSTANTS } from "../../Constants";
export interface IConstructorParams {
augmentation: Augmentation;
readonly cost: number;
readonly time: number;
}
export class GraftableAugmentation {
// The augmentation that this craftable corresponds to
augmentation: Augmentation;
constructor(augmentation: Augmentation) {
this.augmentation = augmentation;
}
get cost(): number {
return this.augmentation.startingCost * CONSTANTS.AugmentationGraftingCostMult;
}
get time(): number {
// Time = 1 hour * log_2(sum(aug multipliers) || 1) + 30 minutes
const antiLog = Math.max(sum(Object.values(this.augmentation.mults)), 1);
const mult = Math.log2(antiLog);
return CONSTANTS.AugmentationGraftingTimeBase * mult + CONSTANTS.MillisecondsPerHalfHour;
}
}

@ -0,0 +1,160 @@
import React, { useState } from "react";
import { Typography, Container, Box, Paper, List, ListItemButton, Button } from "@mui/material";
import { Construction } from "@mui/icons-material";
import { use } from "../../../ui/Context";
import { Money } from "../../../ui/React/Money";
import { ConfirmationModal } from "../../../ui/React/ConfirmationModal";
import { Augmentations } from "../../../Augmentation/Augmentations";
import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames";
import { Settings } from "../../../Settings/Settings";
import { IMap } from "../../../types";
import { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions";
import { LocationName } from "../../../Locations/data/LocationNames";
import { Locations } from "../../../Locations/Locations";
import { CONSTANTS } from "../../../Constants";
import { IPlayer } from "../../IPlayer";
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));
};
export const GraftingRoot = (): React.ReactElement => {
const player = use.Player();
const router = use.Router();
for (const aug of Object.values(Augmentations)) {
const name = aug.name;
const graftableAug = new GraftableAugmentation(aug);
GraftableAugmentations[name] = graftableAug;
}
const [selectedAug, setSelectedAug] = useState(getAvailableAugs(player)[0]);
const [graftOpen, setGraftOpen] = useState(false);
return (
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Button onClick={() => router.toLocation(Locations[LocationName.NewTokyoVitaLife])}>Back</Button>
<Typography variant="h4">Grafting Laboratory</Typography>
<Typography>
You find yourself in a secret laboratory, owned by a mysterious researcher.
<br />
The scientist explains that they've been studying Augmentation grafting, the process of applying Augmentations
without requiring a body reset.
<br />
<br />
Through legally questionable connections, the scientist has access to a vast array of Augmentation blueprints,
even private designs. They offer to build and graft the Augmentations to you, in exchange for both a hefty sum
of money, and being a lab rat.
</Typography>
<Box sx={{ my: 3 }}>
<Typography variant="h5">Graft Augmentations</Typography>
<Paper sx={{ my: 1, width: "fit-content", display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<List sx={{ maxHeight: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}>
{getAvailableAugs(player).map((k, i) => (
<ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
<Typography>{k}</Typography>
</ListItemButton>
))}
</List>
<Box sx={{ m: 1 }}>
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
<Construction sx={{ mr: 1 }} /> {selectedAug}
</Typography>
<Button
onClick={() => setGraftOpen(true)}
sx={{ width: "100%" }}
disabled={player.money < GraftableAugmentations[selectedAug].cost}
>
Graft Augmentation (
<Typography>
<Money money={GraftableAugmentations[selectedAug].cost} player={player} />
</Typography>
)
</Button>
<ConfirmationModal
open={graftOpen}
onClose={() => setGraftOpen(false)}
onConfirm={() => {
const graftableAug = GraftableAugmentations[selectedAug];
player.loseMoney(graftableAug.cost, "augmentations");
player.startGraftAugmentationWork(selectedAug, graftableAug.time);
player.startFocusing();
router.toWork();
}}
confirmationText={
<>
Cancelling grafting will <b>not</b> save grafting progress, and the money you spend will <b>not</b> be
returned.
<br />
<br />
Additionally, grafting an Augmentation will increase the potency of the Entropy virus.
</>
}
/>
<Typography color={Settings.theme.info}>
<b>Time to Graft:</b>{" "}
{convertTimeMsToTimeElapsedString(
GraftableAugmentations[selectedAug].time / (1 + (player.getIntelligenceBonus(3) - 1) / 3),
)}
{/* Use formula so the displayed creation time is accurate to player bonus */}
</Typography>
<Typography sx={{ maxHeight: 305, overflowY: "scroll" }}>
{(() => {
const aug = Augmentations[selectedAug];
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return tooltip;
})()}
</Typography>
</Box>
</Paper>
</Box>
<Box sx={{ my: 3 }}>
<Typography variant="h5">Entropy Virus</Typography>
<Paper sx={{ my: 1, p: 1, width: "fit-content" }}>
<Typography>
<b>Entropy strength:</b> {player.entropy}
<br />
<b>All multipliers decreased by:</b>{" "}
{formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropy) * 100, 3)}% (multiplicative)
</Typography>
</Paper>
<Typography>
When installed on an unconscious individual, Augmentations are scanned by the body on awakening, eliminating
hidden malware. However, grafted Augmentations do not provide this security measure.
<br />
<br />
Individuals who tested Augmentation grafting have reported symptoms of an unknown virus, which they've dubbed
"Entropy". This virus seems to grow more potent with each grafted Augmentation...
</Typography>
</Box>
</Container>
);
};

@ -3,7 +3,6 @@
* Used because at the time of implementation, the PlayerObject * Used because at the time of implementation, the PlayerObject
* cant be converted to TypeScript. * cant be converted to TypeScript.
*/ */
import { Resleeve } from "./Resleeving/Resleeve";
import { Sleeve } from "./Sleeve/Sleeve"; import { Sleeve } from "./Sleeve/Sleeve";
import { IMap } from "../types"; import { IMap } from "../types";
@ -65,7 +64,6 @@ export interface IPlayer {
playtimeSinceLastBitnode: number; playtimeSinceLastBitnode: number;
purchasedServers: any[]; purchasedServers: any[];
queuedAugmentations: IPlayerOwnedAugmentation[]; queuedAugmentations: IPlayerOwnedAugmentation[];
resleeves: Resleeve[];
scriptProdSinceLastAug: number; scriptProdSinceLastAug: number;
sleeves: Sleeve[]; sleeves: Sleeve[];
sleevesFromCovenant: number; sleevesFromCovenant: number;
@ -130,6 +128,8 @@ export interface IPlayer {
factionWorkType: string; factionWorkType: string;
createProgramName: string; createProgramName: string;
timeWorkedCreateProgram: number; timeWorkedCreateProgram: number;
graftAugmentationName: string;
timeWorkedGraftAugmentation: number;
crimeType: string; crimeType: string;
committingCrimeThruSingFn: boolean; committingCrimeThruSingFn: boolean;
singFnCrimeWorkerScript: WorkerScript | null; singFnCrimeWorkerScript: WorkerScript | null;
@ -160,6 +160,8 @@ export interface IPlayer {
workChaExpGainRate: number; workChaExpGainRate: number;
workMoneyLossRate: number; workMoneyLossRate: number;
entropy: number;
// Methods // Methods
work(numCycles: number): boolean; work(numCycles: number): boolean;
workPartTime(numCycles: number): boolean; workPartTime(numCycles: number): boolean;
@ -181,7 +183,7 @@ export interface IPlayer {
canAccessBladeburner(): boolean; canAccessBladeburner(): boolean;
canAccessCorporation(): boolean; canAccessCorporation(): boolean;
canAccessGang(): boolean; canAccessGang(): boolean;
canAccessResleeving(): boolean; canAccessGrafting(): boolean;
canAfford(cost: number): boolean; canAfford(cost: number): boolean;
gainHackingExp(exp: number): void; gainHackingExp(exp: number): void;
gainStrengthExp(exp: number): void; gainStrengthExp(exp: number): void;
@ -286,4 +288,8 @@ export interface IPlayer {
setMult(name: string, mult: number): void; setMult(name: string, mult: number): void;
canAccessCotMG(): boolean; canAccessCotMG(): boolean;
sourceFileLvl(n: number): number; sourceFileLvl(n: number): number;
startGraftAugmentationWork(augmentationName: string, time: number): void;
graftAugmentationWork(numCycles: number): boolean;
finishGraftAugmentationWork(cancelled: boolean): string;
applyEntropy(stacks?: number): void;
} }

@ -6,7 +6,6 @@ import * as generalMethods from "./PlayerObjectGeneralMethods";
import * as serverMethods from "./PlayerObjectServerMethods"; import * as serverMethods from "./PlayerObjectServerMethods";
import { IMap } from "../../types"; import { IMap } from "../../types";
import { Resleeve } from "../Resleeving/Resleeve";
import { Sleeve } from "../Sleeve/Sleeve"; import { Sleeve } from "../Sleeve/Sleeve";
import { IPlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile"; import { IPlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile";
import { Exploit } from "../../Exploits/Exploit"; import { Exploit } from "../../Exploits/Exploit";
@ -72,7 +71,6 @@ export class PlayerObject implements IPlayer {
playtimeSinceLastBitnode: number; playtimeSinceLastBitnode: number;
purchasedServers: any[]; purchasedServers: any[];
queuedAugmentations: IPlayerOwnedAugmentation[]; queuedAugmentations: IPlayerOwnedAugmentation[];
resleeves: Resleeve[];
scriptProdSinceLastAug: number; scriptProdSinceLastAug: number;
sleeves: Sleeve[]; sleeves: Sleeve[];
sleevesFromCovenant: number; sleevesFromCovenant: number;
@ -139,6 +137,8 @@ export class PlayerObject implements IPlayer {
factionWorkType: string; factionWorkType: string;
createProgramName: string; createProgramName: string;
timeWorkedCreateProgram: number; timeWorkedCreateProgram: number;
graftAugmentationName: string;
timeWorkedGraftAugmentation: number;
crimeType: string; crimeType: string;
committingCrimeThruSingFn: boolean; committingCrimeThruSingFn: boolean;
singFnCrimeWorkerScript: WorkerScript | null; singFnCrimeWorkerScript: WorkerScript | null;
@ -169,6 +169,8 @@ export class PlayerObject implements IPlayer {
workChaExpGainRate: number; workChaExpGainRate: number;
workMoneyLossRate: number; workMoneyLossRate: number;
entropy: number;
// Methods // Methods
work: (numCycles: number) => boolean; work: (numCycles: number) => boolean;
workPartTime: (numCycles: number) => boolean; workPartTime: (numCycles: number) => boolean;
@ -190,7 +192,7 @@ export class PlayerObject implements IPlayer {
canAccessBladeburner: () => boolean; canAccessBladeburner: () => boolean;
canAccessCorporation: () => boolean; canAccessCorporation: () => boolean;
canAccessGang: () => boolean; canAccessGang: () => boolean;
canAccessResleeving: () => boolean; canAccessGrafting: () => boolean;
canAfford: (cost: number) => boolean; canAfford: (cost: number) => boolean;
gainHackingExp: (exp: number) => void; gainHackingExp: (exp: number) => void;
gainStrengthExp: (exp: number) => void; gainStrengthExp: (exp: number) => void;
@ -296,6 +298,10 @@ export class PlayerObject implements IPlayer {
setMult: (name: string, mult: number) => void; setMult: (name: string, mult: number) => void;
canAccessCotMG: () => boolean; canAccessCotMG: () => boolean;
sourceFileLvl: (n: number) => number; sourceFileLvl: (n: number) => number;
startGraftAugmentationWork: (augmentationName: string, time: number) => void;
graftAugmentationWork: (numCycles: number) => boolean;
finishGraftAugmentationWork: (cancelled: boolean) => string;
applyEntropy: (stacks?: number) => void;
constructor() { constructor() {
//Skills and stats //Skills and stats
@ -419,6 +425,9 @@ export class PlayerObject implements IPlayer {
this.createProgramName = ""; this.createProgramName = "";
this.createProgramReqLvl = 0; this.createProgramReqLvl = 0;
this.graftAugmentationName = "";
this.timeWorkedGraftAugmentation = 0;
this.className = ""; this.className = "";
this.crimeType = ""; this.crimeType = "";
@ -457,11 +466,12 @@ export class PlayerObject implements IPlayer {
// Sleeves & Re-sleeving // Sleeves & Re-sleeving
this.sleeves = []; this.sleeves = [];
this.resleeves = [];
this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenan; this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenan;
//bitnode //bitnode
this.bitNodeN = 1; this.bitNodeN = 1;
this.entropy = 0;
//Used to store the last update time. //Used to store the last update time.
this.lastUpdate = 0; this.lastUpdate = 0;
this.lastSave = 0; this.lastSave = 0;
@ -483,11 +493,11 @@ export class PlayerObject implements IPlayer {
// Let's get a hash of some semi-random stuff so we have something unique. // Let's get a hash of some semi-random stuff so we have something unique.
this.identifier = cyrb53( this.identifier = cyrb53(
"I-" + "I-" +
new Date().getTime() + new Date().getTime() +
navigator.userAgent + navigator.userAgent +
window.innerWidth + window.innerWidth +
window.innerHeight + window.innerHeight +
getRandomInt(100, 999), getRandomInt(100, 999),
); );
this.init = generalMethods.init; this.init = generalMethods.init;
@ -541,6 +551,9 @@ export class PlayerObject implements IPlayer {
this.startCreateProgramWork = generalMethods.startCreateProgramWork; this.startCreateProgramWork = generalMethods.startCreateProgramWork;
this.createProgramWork = generalMethods.createProgramWork; this.createProgramWork = generalMethods.createProgramWork;
this.finishCreateProgramWork = generalMethods.finishCreateProgramWork; this.finishCreateProgramWork = generalMethods.finishCreateProgramWork;
this.startGraftAugmentationWork = generalMethods.startGraftAugmentationWork;
this.graftAugmentationWork = generalMethods.craftAugmentationWork;
this.finishGraftAugmentationWork = generalMethods.finishGraftAugmentationWork;
this.startClass = generalMethods.startClass; this.startClass = generalMethods.startClass;
this.takeClass = generalMethods.takeClass; this.takeClass = generalMethods.takeClass;
this.finishClass = generalMethods.finishClass; this.finishClass = generalMethods.finishClass;
@ -577,7 +590,7 @@ export class PlayerObject implements IPlayer {
this.gainCodingContractReward = generalMethods.gainCodingContractReward; this.gainCodingContractReward = generalMethods.gainCodingContractReward;
this.travel = generalMethods.travel; this.travel = generalMethods.travel;
this.gotoLocation = generalMethods.gotoLocation; this.gotoLocation = generalMethods.gotoLocation;
this.canAccessResleeving = generalMethods.canAccessResleeving; this.canAccessGrafting = generalMethods.canAccessGrafting;
this.giveExploit = generalMethods.giveExploit; this.giveExploit = generalMethods.giveExploit;
this.giveAchievement = generalMethods.giveAchievement; this.giveAchievement = generalMethods.giveAchievement;
this.getIntelligenceBonus = generalMethods.getIntelligenceBonus; this.getIntelligenceBonus = generalMethods.getIntelligenceBonus;
@ -611,6 +624,8 @@ export class PlayerObject implements IPlayer {
this.canAccessCotMG = generalMethods.canAccessCotMG; this.canAccessCotMG = generalMethods.canAccessCotMG;
this.sourceFileLvl = generalMethods.sourceFileLvl; this.sourceFileLvl = generalMethods.sourceFileLvl;
this.applyEntropy = augmentationMethods.applyEntropy;
} }
/** /**

@ -5,6 +5,8 @@ import { IPlayer } from "../IPlayer";
import { Augmentation } from "../../Augmentation/Augmentation"; 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, installed = false): boolean {
const augName: string = aug instanceof Augmentation ? aug.name : aug; const augName: string = aug instanceof Augmentation ? aug.name : aug;
@ -24,3 +26,14 @@ export function hasAugmentation(this: IPlayer, aug: string | Augmentation, insta
return false; return false;
} }
export function applyEntropy(this: IPlayer, stacks = 1): void {
// Re-apply all multipliers
this.reapplyAllAugmentations();
this.reapplyAllSourceFiles();
const newMultipliers = calculateEntropy(this, stacks);
for (const [mult, val] of Object.entries(newMultipliers)) {
this.setMult(mult, val);
}
}

@ -121,8 +121,6 @@ export function prestigeAugmentation(this: PlayerObject): void {
this.queuedAugmentations = []; this.queuedAugmentations = [];
this.resleeves = [];
const numSleeves = Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant; const numSleeves = Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant;
if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves; if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves;
for (let i = this.sleeves.length; i < numSleeves; i++) { for (let i = this.sleeves.length; i < numSleeves; i++) {
@ -182,6 +180,7 @@ export function prestigeAugmentation(this: PlayerObject): void {
} }
export function prestigeSourceFile(this: IPlayer): void { export function prestigeSourceFile(this: IPlayer): void {
this.entropy = 0;
this.prestigeAugmentation(); this.prestigeAugmentation();
this.karma = 0; this.karma = 0;
// Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists) // Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists)
@ -529,10 +528,12 @@ export function resetWorkStatus(this: IPlayer, generalType?: string, group?: str
this.timeWorked = 0; this.timeWorked = 0;
this.timeWorkedCreateProgram = 0; this.timeWorkedCreateProgram = 0;
this.timeWorkedGraftAugmentation = 0;
this.currentWorkFactionName = ""; this.currentWorkFactionName = "";
this.currentWorkFactionDescription = ""; this.currentWorkFactionDescription = "";
this.createProgramName = ""; this.createProgramName = "";
this.graftAugmentationName = "";
this.className = ""; this.className = "";
this.workType = ""; this.workType = "";
} }
@ -609,6 +610,10 @@ export function process(this: IPlayer, router: IRouter, numCycles = 1): void {
if (this.workPartTime(numCycles)) { if (this.workPartTime(numCycles)) {
router.toCity(); router.toCity();
} }
} else if (this.workType === CONSTANTS.WorkTypeGraftAugmentation) {
if (this.graftAugmentationWork(numCycles)) {
router.toGrafting();
}
} else if (this.work(numCycles)) { } else if (this.work(numCycles)) {
router.toCity(); router.toCity();
} }
@ -1329,6 +1334,59 @@ export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): stri
this.resetWorkStatus(); this.resetWorkStatus();
return "You've finished creating " + programName + "! The new program can be found on your home computer."; return "You've finished creating " + programName + "! The new program can be found on your home computer.";
} }
export function startGraftAugmentationWork(this: IPlayer, augmentationName: string, time: number): void {
this.resetWorkStatus();
this.isWorking = true;
this.workType = CONSTANTS.WorkTypeGraftAugmentation;
this.timeNeededToCompleteWork = time;
this.graftAugmentationName = augmentationName;
}
export function craftAugmentationWork(this: IPlayer, numCycles: number): boolean {
let focusBonus = 1;
if (!this.hasAugmentation(AugmentationNames.NeuroreceptorManager)) {
focusBonus = this.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
let skillMult = 1 + (this.getIntelligenceBonus(3) - 1) / 3;
skillMult *= focusBonus;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.timeWorkedGraftAugmentation += CONSTANTS._idleSpeed * numCycles * skillMult;
if (this.timeWorkedGraftAugmentation >= this.timeNeededToCompleteWork) {
this.finishGraftAugmentationWork(false);
return true;
}
return false;
}
export function finishGraftAugmentationWork(this: IPlayer, cancelled: boolean): string {
const augName = this.graftAugmentationName;
if (cancelled === false) {
dialogBoxCreate(
`You've finished crafting ${augName}.<br>The augmentation has been grafted to your body, but you feel a bit off.`,
);
applyAugmentation(Augmentations[augName]);
this.entropy += 1;
this.applyEntropy(this.entropy);
} else {
dialogBoxCreate(`You cancelled the crafting of ${augName}.<br>Your money was not returned to you.`);
}
// Intelligence gain
if (!cancelled) {
this.gainIntelligenceExp((CONSTANTS.IntelligenceGraftBaseExpGain * this.timeWorked) / 10000);
}
this.isWorking = false;
this.resetWorkStatus();
return `Grafting of ${augName} has ended.`;
}
/* Studying/Taking Classes */ /* Studying/Taking Classes */
export function startClass(this: IPlayer, costMult: number, expMult: number, className: string): void { export function startClass(this: IPlayer, costMult: number, expMult: number, className: string): void {
this.resetWorkStatus(); this.resetWorkStatus();
@ -2640,7 +2698,7 @@ export function gotoLocation(this: IPlayer, to: LocationName): boolean {
return true; return true;
} }
export function canAccessResleeving(this: IPlayer): boolean { export function canAccessGrafting(this: IPlayer): boolean {
return this.bitNodeN === 10 || SourceFileFlags[10] > 0; return this.bitNodeN === 10 || SourceFileFlags[10] > 0;
} }

@ -1,10 +0,0 @@
Implements the Re-sleeving feature, which allows players to purchase a new body
that comes with pre-existing Augmentations and experience. Note that purchasing
a new body causes you to lose all of your old Augmentations and experience
This feature is introduced in BitNode-10, and destroying BitNode-10 allows
the user to use it in other BitNodes (provided that they purchase the required
cortical stack Augmentation)
While they are based on the same concept, this feature is different than the
"Duplicate Sleeve" mechanic (which is referred to as just "Sleeve" in the source code).

@ -1,63 +0,0 @@
/**
* Implements the Resleeve class, which defines a new body
* that the player can "re-sleeve" into.
*/
import { Person } from "../Person";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
export class Resleeve extends Person {
constructor() {
super();
}
getCost(): number {
// Each experience point adds this to the cost
const CostPerExp = 25e3;
// Final cost is multiplied by this constant ^ # Augs
const NumAugsExponent = 1.2;
// Get total exp in this re-sleeve
const totalExp: number =
this.hacking_exp +
this.strength_exp +
this.defense_exp +
this.dexterity_exp +
this.agility_exp +
this.charisma_exp;
// Get total base Augmentation cost for this re-sleeve
let totalAugmentationCost = 0;
for (let i = 0; i < this.augmentations.length; ++i) {
const aug: Augmentation | null = Augmentations[this.augmentations[i].name];
if (aug == null) {
console.error(`Could not find Augmentation ${this.augmentations[i].name}`);
continue;
}
totalAugmentationCost += aug.startingCost;
}
return totalExp * CostPerExp + totalAugmentationCost * Math.pow(NumAugsExponent, this.augmentations.length);
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): any {
return Generic_toJSON("Resleeve", this);
}
/**
* Initiatizes a Resleeve object from a JSON save state.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromJSON(value: any): Resleeve {
return Generic_fromJSON(Resleeve, value.data);
}
}
Reviver.constructors.Resleeve = Resleeve;

@ -1,166 +0,0 @@
import React, { useState } from "react";
import { IPlayer } from "../../IPlayer";
import { Resleeve } from "../Resleeve";
import { Augmentations } from "../../../Augmentation/Augmentations";
import { purchaseResleeve } from "../Resleeving";
import { Money } from "../../../ui/React/Money";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import Grid from "@mui/material/Grid";
interface IProps {
resleeve: Resleeve;
player: IPlayer;
}
export function ResleeveElem(props: IProps): React.ReactElement {
const [aug, setAug] = useState(props.resleeve.augmentations[0].name);
function openStats(): void {
dialogBoxCreate(
<>
<Typography variant="h5" color="primary">
Total Multipliers:
</Typography>
<Typography>
Hacking Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_mult)}
<br />
Hacking Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_exp_mult)}
<br />
Strength Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_mult)}
<br />
Strength Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_exp_mult)}
<br />
Defense Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_mult)}
<br />
Defense Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_exp_mult)}
<br />
Dexterity Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_mult)}
<br />
Dexterity Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_exp_mult)}
<br />
Agility Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_mult)}
<br />
Agility Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_exp_mult)}
<br />
Charisma Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_mult)}
<br />
Charisma Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_exp_mult)}
<br />
Hacking Chance multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_chance_mult)}
<br />
Hacking Speed multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_speed_mult)}
<br />
Hacking Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_money_mult)}
<br />
Hacking Growth multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_grow_mult)}
<br />
Salary multiplier: {numeralWrapper.formatPercentage(props.resleeve.work_money_mult)}
<br />
Company Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.company_rep_mult)}
<br />
Faction Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.faction_rep_mult)}
<br />
Crime Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_money_mult)}
<br />
Crime Success multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_success_mult)}
<br />
Hacknet Income multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_money_mult)}
<br />
Hacknet Purchase Cost multiplier:
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_purchase_cost_mult)}
<br />
Hacknet Level Upgrade Cost multiplier:
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_level_cost_mult)}
<br />
Hacknet Ram Upgrade Cost multiplier:
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_ram_cost_mult)}
<br />
Hacknet Core Upgrade Cost multiplier:
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_core_cost_mult)}
<br />
Bladeburner Max Stamina multiplier:
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_max_stamina_mult)}
<br />
Bladeburner Stamina Gain multiplier:
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_stamina_gain_mult)}
<br />
Bladeburner Field Analysis multiplier:
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_analysis_mult)}
<br />
Bladeburner Success Chance multiplier:
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_success_chance_mult)}
</Typography>
</>,
);
}
function onAugChange(event: SelectChangeEvent<string>): void {
setAug(event.target.value);
}
const currentAug = Augmentations[aug];
const cost = props.resleeve.getCost();
function purchase(): void {
if (!purchaseResleeve(props.resleeve, props.player)) return;
dialogBoxCreate(
<>
You re-sleeved for <Money money={cost} />!
</>,
);
}
return (
<Paper sx={{ my: 1 }}>
<Grid container>
<Grid item xs={3}>
<Typography>
Hacking: {numeralWrapper.formatSkill(props.resleeve.hacking)} (
{numeralWrapper.formatExp(props.resleeve.hacking_exp)} exp)
<br />
Strength: {numeralWrapper.formatSkill(props.resleeve.strength)} (
{numeralWrapper.formatExp(props.resleeve.strength_exp)} exp)
<br />
Defense: {numeralWrapper.formatSkill(props.resleeve.defense)} (
{numeralWrapper.formatExp(props.resleeve.defense_exp)} exp)
<br />
Dexterity: {numeralWrapper.formatSkill(props.resleeve.dexterity)} (
{numeralWrapper.formatExp(props.resleeve.dexterity_exp)} exp)
<br />
Agility: {numeralWrapper.formatSkill(props.resleeve.agility)} (
{numeralWrapper.formatExp(props.resleeve.agility_exp)} exp)
<br />
Charisma: {numeralWrapper.formatSkill(props.resleeve.charisma)} (
{numeralWrapper.formatExp(props.resleeve.charisma_exp)} exp)
<br /># Augmentations: {props.resleeve.augmentations.length}
</Typography>
<Button onClick={openStats}>Multipliers</Button>
</Grid>
<Grid item xs={6}>
<Select value={aug} onChange={onAugChange}>
{props.resleeve.augmentations.map((aug) => (
<MenuItem key={aug.name} value={aug.name}>
{aug.name}
</MenuItem>
))}
</Select>
<Typography>{currentAug !== undefined && currentAug.info}</Typography>
</Grid>
<Grid item xs={3}>
<Typography>
It costs <Money money={cost} player={props.player} /> to purchase this Sleeve.
</Typography>
<Button onClick={purchase}>Purchase</Button>
</Grid>
</Grid>
</Paper>
);
}

@ -1,124 +0,0 @@
import React, { useState } from "react";
import { generateResleeves } from "../Resleeving";
import { Resleeve } from "../Resleeve";
import { ResleeveElem } from "./ResleeveElem";
import { use } from "../../../ui/Context";
import Typography from "@mui/material/Typography";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import Box from "@mui/material/Box";
const SortOption: {
[key: string]: string | undefined;
Cost: string;
Hacking: string;
Strength: string;
Defense: string;
Dexterity: string;
Agility: string;
Charisma: string;
AverageCombatStats: string;
AverageAllStats: string;
TotalNumAugmentations: string;
} = {
Cost: "Cost",
Hacking: "Hacking Level",
Strength: "Strength Level",
Defense: "Defense Level",
Dexterity: "Dexterity Level",
Agility: "Agility Level",
Charisma: "Charisma Level",
AverageCombatStats: "Average Combat Stats",
AverageAllStats: "Average Stats",
TotalNumAugmentations: "Number of Augmentations",
};
// Helper function for averaging
function getAverage(...values: number[]): number {
let sum = 0;
for (let i = 0; i < values.length; ++i) {
sum += values[i];
}
return sum / values.length;
}
const SortFunctions: {
[key: string]: ((a: Resleeve, b: Resleeve) => number) | undefined;
Cost: (a: Resleeve, b: Resleeve) => number;
Hacking: (a: Resleeve, b: Resleeve) => number;
Strength: (a: Resleeve, b: Resleeve) => number;
Defense: (a: Resleeve, b: Resleeve) => number;
Dexterity: (a: Resleeve, b: Resleeve) => number;
Agility: (a: Resleeve, b: Resleeve) => number;
Charisma: (a: Resleeve, b: Resleeve) => number;
AverageCombatStats: (a: Resleeve, b: Resleeve) => number;
AverageAllStats: (a: Resleeve, b: Resleeve) => number;
TotalNumAugmentations: (a: Resleeve, b: Resleeve) => number;
} = {
Cost: (a: Resleeve, b: Resleeve): number => a.getCost() - b.getCost(),
Hacking: (a: Resleeve, b: Resleeve): number => a.hacking - b.hacking,
Strength: (a: Resleeve, b: Resleeve): number => a.strength - b.strength,
Defense: (a: Resleeve, b: Resleeve): number => a.defense - b.defense,
Dexterity: (a: Resleeve, b: Resleeve): number => a.dexterity - b.dexterity,
Agility: (a: Resleeve, b: Resleeve): number => a.agility - b.agility,
Charisma: (a: Resleeve, b: Resleeve): number => a.charisma - b.charisma,
AverageCombatStats: (a: Resleeve, b: Resleeve): number =>
getAverage(a.strength, a.defense, a.dexterity, a.agility) -
getAverage(b.strength, b.defense, b.dexterity, b.agility),
AverageAllStats: (a: Resleeve, b: Resleeve): number =>
getAverage(a.hacking, a.strength, a.defense, a.dexterity, a.agility, a.charisma) -
getAverage(b.hacking, b.strength, b.defense, b.dexterity, b.agility, b.charisma),
TotalNumAugmentations: (a: Resleeve, b: Resleeve): number => a.augmentations.length - b.augmentations.length,
};
export function ResleeveRoot(): React.ReactElement {
const player = use.Player();
const [sort, setSort] = useState(SortOption.Cost);
// Randomly create all Resleeves if they dont already exist
if (player.resleeves.length === 0) {
player.resleeves = generateResleeves();
}
function onSortChange(event: SelectChangeEvent<string>): void {
setSort(event.target.value);
}
const sortFunction = SortFunctions[sort];
if (sortFunction === undefined) throw new Error(`sort function '${sort}' is undefined`);
player.resleeves.sort(sortFunction);
return (
<>
<Typography>
Re-sleeving is the process of digitizing and transferring your consciousness into a new human body, or 'sleeve'.
Here at VitaLife, you can purchase new specially-engineered bodies for the re-sleeve process. Many of these
bodies even come with genetic and cybernetic Augmentations!
<br />
<br />
Re-sleeving will change your experience for every stat. It will also REMOVE all of your currently-installed
Augmentations, and replace them with the ones provided by the purchased sleeve. However, Augmentations that you
have purchased but not installed will NOT be removed. If you have purchased an Augmentation and then re-sleeve
into a body which already has that Augmentation, it will be removed (since you cannot have duplicate
Augmentations).
<br />
<br />
NOTE: The stats and multipliers displayed on this page do NOT include your bonuses from Source-File.
</Typography>
<Box display="flex" alignItems="center">
<Typography>Sort By: </Typography>
<Select value={sort} onChange={onSortChange}>
{Object.keys(SortOption).map((opt) => (
<MenuItem key={opt} value={opt}>
{SortOption[opt]}
</MenuItem>
))}
</Select>
</Box>
{player.resleeves.map((resleeve, i) => (
<ResleeveElem key={i} player={player} resleeve={resleeve} />
))}
</>
);
}

@ -108,6 +108,9 @@ export function prestigeAugmentation(): void {
// Messages // Messages
initMessages(); initMessages();
// Apply entropy from grafting
Player.applyEntropy(Player.entropy);
// Gang // Gang
const gang = Player.gang; const gang = Player.gang;
if (Player.inGang() && gang !== null) { if (Player.inGang() && gang !== null) {

@ -393,6 +393,11 @@ function evaluateVersionCompatibility(ver: string | number): void {
} }
} }
} }
if (ver < 12) {
if (anyPlayer.resleeves !== undefined) {
delete anyPlayer.resleeves;
}
}
} }
} }

@ -225,6 +225,9 @@ async function parseOnlyRamCalculate(
} else if (ref in workerScript.env.vars.ui) { } else if (ref in workerScript.env.vars.ui) {
func = workerScript.env.vars.ui[ref]; func = workerScript.env.vars.ui[ref];
refDetail = `ui.${ref}`; refDetail = `ui.${ref}`;
} else if (ref in workerScript.env.vars.grafting) {
func = workerScript.env.vars.grafting[ref];
refDetail = `grafting.${ref}`;
} else { } else {
func = workerScript.env.vars[ref]; func = workerScript.env.vars[ref];
refDetail = `${ref}`; refDetail = `${ref}`;

@ -95,6 +95,7 @@ interface Player {
tor: boolean; tor: boolean;
hasCorporation: boolean; hasCorporation: boolean;
inBladeburner: boolean; inBladeburner: boolean;
entropy: number;
} }
/** /**
@ -666,6 +667,10 @@ export interface CharacterMult {
agility: number; agility: number;
/** Agility exp */ /** Agility exp */
agilityExp: number; agilityExp: number;
/** Charisma stat */
charisma: number;
/** Charisma exp */
charismaExp: number;
/** Company reputation */ /** Company reputation */
companyRep: number; companyRep: number;
/** Money earned from crimes */ /** Money earned from crimes */
@ -706,10 +711,10 @@ export interface CharacterInfo {
factions: string[]; factions: string[];
/** Current health points */ /** Current health points */
hp: number; hp: number;
/** Array of all companies at which you have jobs */ /** Array of all jobs */
company: string[]; jobs: string[];
/** Array of job positions for all companies you are employed at. Same order as 'jobs' */ /** Array of job positions for all companies you are employed at. Same order as 'jobs' */
jobTitle: string[]; jobTitles: string[];
/** Maximum health points */ /** Maximum health points */
maxHp: number; maxHp: number;
/** Boolean indicating whether or not you have a tor router */ /** Boolean indicating whether or not you have a tor router */
@ -734,6 +739,18 @@ export interface CharacterInfo {
workRepGain: number; workRepGain: number;
/** Money earned so far from work, if applicable */ /** Money earned so far from work, if applicable */
workMoneyGain: number; workMoneyGain: number;
/** total hacking exp */
hackingExp: number;
/** total strength exp */
strengthExp: number;
/** total defense exp */
defenseExp: number;
/** total dexterity exp */
dexterityExp: number;
/** total agility exp */
agilityExp: number;
/** total charisma exp */
charismaExp: number;
} }
/** /**
@ -1597,7 +1614,7 @@ export interface Singularity {
* purchasing a TOR router using this function is the same as if you were to * purchasing a TOR router using this function is the same as if you were to
* manually purchase one. * manually purchase one.
* *
* @returns True if actions is successful, false otherwise. * @returns True if actions is successful or you already own TOR router, false otherwise.
*/ */
purchaseTor(): boolean; purchaseTor(): boolean;
@ -2198,11 +2215,8 @@ export interface Singularity {
* Hospitalize the player. * Hospitalize the player.
* @remarks * @remarks
* RAM cost: 0.25 GB * 16/4/1 * RAM cost: 0.25 GB * 16/4/1
*
*
* @returns The cost of the hospitalization.
*/ */
hospitalize(): number; hospitalize(): void;
/** /**
* Soft reset the game. * Soft reset the game.
@ -3719,6 +3733,49 @@ export interface Sleeve {
purchaseSleeveAug(sleeveNumber: number, augName: string): boolean; purchaseSleeveAug(sleeveNumber: number, augName: string): boolean;
} }
/**
* Grafting API
* @remarks
* This API requires Source-File 10 to use.
* @public
*/
export interface Grafting {
/**
* Retrieve the grafting cost of an aug.
* @remarks
* RAM cost: 3.75 GB
*
* @param augName - Name of the aug to check the price of. Must be an exact match.
* @returns The cost required to graft the named augmentation.
* @throws Will error if an invalid Augmentation name is provided.
*/
getAugmentationGraftPrice(augName: string): number;
/**
* Retrieves the time required to graft an aug.
* @remarks
* RAM cost: 3.75 GB
*
* @param augName - Name of the aug to check the grafting time of. Must be an exact match.
* @returns The time required, in millis, to graft the named augmentation.
* @throws Will error if an invalid Augmentation name is provided.
*/
getAugmentationGraftTime(augName: string): number;
/**
* Begins grafting the named aug. You must be in New Tokyo to use this.
* @remarks
* RAM cost: 7.5 GB
*
* @param augName - The name of the aug to begin grafting. Must be an exact match.
* @param focus - Acquire player focus on this Augmentation grafting. Optional. Defaults to true.
* @returns True if the aug successfully began grafting, false otherwise (e.g. not enough money, or
* invalid Augmentation name provided).
* @throws Will error if called while you are not in New Tokyo.
*/
graftAugmentation(augName: string, focus?: boolean): boolean;
}
/** /**
* Skills formulas * Skills formulas
* @public * @public
@ -4028,14 +4085,14 @@ interface Stanek {
* RAM cost: 0.4 GB * RAM cost: 0.4 GB
* @returns The width of the gift. * @returns The width of the gift.
*/ */
width(): number; giftWidth(): number;
/** /**
* Stanek's Gift height. * Stanek's Gift height.
* @remarks * @remarks
* RAM cost: 0.4 GB * RAM cost: 0.4 GB
* @returns The height of the gift. * @returns The height of the gift.
*/ */
height(): number; giftHeight(): number;
/** /**
* Charge a fragment, increasing its power. * Charge a fragment, increasing its power.
@ -4045,7 +4102,7 @@ interface Stanek {
* @param rootY - rootY Root Y against which to align the top left of the fragment. * @param rootY - rootY Root Y against which to align the top left of the fragment.
* @returns Promise that lasts until the charge action is over. * @returns Promise that lasts until the charge action is over.
*/ */
charge(rootX: number, rootY: number): Promise<void>; chargeFragment(rootX: number, rootY: number): Promise<void>;
/** /**
* List possible fragments. * List possible fragments.
@ -4070,7 +4127,7 @@ interface Stanek {
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0 GB
*/ */
clear(): void; clearGift(): void;
/** /**
* Check if fragment can be placed at specified location. * Check if fragment can be placed at specified location.
@ -4083,7 +4140,7 @@ interface Stanek {
* @param fragmentId - fragmentId ID of the fragment to place. * @param fragmentId - fragmentId ID of the fragment to place.
* @returns true if the fragment can be placed at that position. false otherwise. * @returns true if the fragment can be placed at that position. false otherwise.
*/ */
canPlace(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean; canPlaceFragment(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
/** /**
* Place fragment on Stanek's Gift. * Place fragment on Stanek's Gift.
* @remarks * @remarks
@ -4095,7 +4152,7 @@ interface Stanek {
* @param fragmentId - ID of the fragment to place. * @param fragmentId - ID of the fragment to place.
* @returns true if the fragment can be placed at that position. false otherwise. * @returns true if the fragment can be placed at that position. false otherwise.
*/ */
place(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean; placeFragment(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
/** /**
* Get placed fragment at location. * Get placed fragment at location.
* @remarks * @remarks
@ -4105,7 +4162,7 @@ interface Stanek {
* @param rootY - Y against which to align the top left of the fragment. * @param rootY - Y against which to align the top left of the fragment.
* @returns The fragment at [rootX, rootY], if any. * @returns The fragment at [rootX, rootY], if any.
*/ */
get(rootX: number, rootY: number): ActiveFragment | undefined; getFragment(rootX: number, rootY: number): ActiveFragment | undefined;
/** /**
* Remove fragment at location. * Remove fragment at location.
@ -4116,7 +4173,7 @@ interface Stanek {
* @param rootY - Y against which to align the top left of the fragment. * @param rootY - Y against which to align the top left of the fragment.
* @returns The fragment at [rootX, rootY], if any. * @returns The fragment at [rootX, rootY], if any.
*/ */
remove(rootX: number, rootY: number): boolean; removeFragment(rootX: number, rootY: number): boolean;
} }
/** /**
@ -4280,6 +4337,19 @@ export interface NS extends Singularity {
*/ */
readonly ui: UserInterface; readonly ui: UserInterface;
/**
* Namespace for singularity functions.
* RAM cost: 0 GB
*/
readonly singularity: Singularity;
/**
* Namespace for grafting functions.
* @remarks
* RAM cost: 0 GB
*/
readonly grafting: Grafting;
/** /**
* Arguments passed into the script. * Arguments passed into the script.
* *

@ -118,6 +118,11 @@ interface IDefaultSettings {
*/ */
SuppressSavedGameToast: boolean; SuppressSavedGameToast: boolean;
/**
* Whether the user should be displayed a toast warning when the autosave is disabled.
*/
SuppressAutosaveDisabledWarnings: boolean;
/* /*
* Whether the game should skip saving the running scripts for late game * Whether the game should skip saving the running scripts for late game
*/ */
@ -197,6 +202,7 @@ export const defaultSettings: IDefaultSettings = {
SuppressBladeburnerPopup: false, SuppressBladeburnerPopup: false,
SuppressTIXPopup: false, SuppressTIXPopup: false,
SuppressSavedGameToast: false, SuppressSavedGameToast: false,
SuppressAutosaveDisabledWarnings: false,
UseIEC60027_2: false, UseIEC60027_2: false,
ExcludeRunningScriptsFromSave: false, ExcludeRunningScriptsFromSave: false,
IsSidebarOpened: true, IsSidebarOpened: true,
@ -235,6 +241,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup, SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
SuppressTIXPopup: defaultSettings.SuppressTIXPopup, SuppressTIXPopup: defaultSettings.SuppressTIXPopup,
SuppressSavedGameToast: defaultSettings.SuppressSavedGameToast, SuppressSavedGameToast: defaultSettings.SuppressSavedGameToast,
SuppressAutosaveDisabledWarnings: defaultSettings.SuppressAutosaveDisabledWarnings,
UseIEC60027_2: defaultSettings.UseIEC60027_2, UseIEC60027_2: defaultSettings.UseIEC60027_2,
ExcludeRunningScriptsFromSave: defaultSettings.ExcludeRunningScriptsFromSave, ExcludeRunningScriptsFromSave: defaultSettings.ExcludeRunningScriptsFromSave,
IsSidebarOpened: defaultSettings.IsSidebarOpened, IsSidebarOpened: defaultSettings.IsSidebarOpened,

@ -617,7 +617,7 @@ export function SidebarRoot(props: IProps): React.ReactElement {
key={"City"} key={"City"}
className={clsx({ className={clsx({
[classes.active]: [classes.active]:
props.page === Page.City || props.page === Page.Resleeves || props.page === Page.Location, props.page === Page.City || props.page === Page.Grafting || props.page === Page.Location,
})} })}
onClick={clickCity} onClick={clickCity}
> >

@ -167,8 +167,8 @@ SourceFiles["SourceFile10"] = new SourceFile(
10, 10,
( (
<> <>
This Source-File unlocks Sleeve technology in other BitNodes. Each level of this Source-File also grants you a This Source-File unlocks Sleeve technology, and the Grafting API in other BitNodes.
Duplicate Sleeve Each level of this Source-File also grants you a Duplicate Sleeve
</> </>
), ),
); );

@ -36,7 +36,7 @@ export function connect(
terminal.connectToServer(player, hostname); terminal.connectToServer(player, hostname);
return; return;
} }
terminal.error(`Cannot directly connect to ${hostname}`); terminal.error(`Cannot directly connect to ${hostname}. Make sure the server is backdoored or adjacent to your current Server`);
} else { } else {
terminal.error("Host not found"); terminal.error("Host not found");
} }

@ -15,6 +15,7 @@ import { Factions, initFactions } from "./Faction/Factions";
import { staneksGift } from "./CotMG/Helper"; import { staneksGift } from "./CotMG/Helper";
import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers"; import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers";
import { Router } from "./ui/GameRoot"; import { Router } from "./ui/GameRoot";
import { Page } from "./ui/Router";
import { SetupTextEditor } from "./ScriptEditor/ui/ScriptEditorRoot"; import { SetupTextEditor } from "./ScriptEditor/ui/ScriptEditorRoot";
import { import {
@ -48,7 +49,8 @@ import { calculateAchievements } from "./Achievements/Achievements";
import React from "react"; import React from "react";
import { setupUncaughtPromiseHandler } from "./UncaughtPromiseHandler"; import { setupUncaughtPromiseHandler } from "./UncaughtPromiseHandler";
import { Typography } from "@mui/material"; import { Button, Typography } from "@mui/material";
import { SnackbarEvents } from "./ui/React/Snackbar";
const Engine: { const Engine: {
_lastUpdate: number; _lastUpdate: number;
@ -187,7 +189,8 @@ const Engine: {
Settings.AutosaveInterval = 60; Settings.AutosaveInterval = 60;
} }
if (Settings.AutosaveInterval === 0) { if (Settings.AutosaveInterval === 0) {
Engine.Counters.autoSaveCounter = Infinity; warnAutosaveDisabled();
Engine.Counters.autoSaveCounter = 60 * 5; // Let's check back in a bit
} else { } else {
Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5; Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5;
saveObject.saveGame(!Settings.SuppressSavedGameToast); saveObject.saveGame(!Settings.SuppressSavedGameToast);
@ -260,6 +263,9 @@ const Engine: {
initSymbolToStockMap(); initSymbolToStockMap();
} }
// Apply penalty for entropy accumulation
Player.applyEntropy(Player.entropy);
// Calculate the number of cycles have elapsed while offline // Calculate the number of cycles have elapsed while offline
Engine._lastUpdate = new Date().getTime(); Engine._lastUpdate = new Date().getTime();
const lastUpdate = Player.lastUpdate; const lastUpdate = Player.lastUpdate;
@ -299,6 +305,8 @@ const Engine: {
Player.commitCrime(numCyclesOffline); Player.commitCrime(numCyclesOffline);
} else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) { } else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
Player.workPartTime(numCyclesOffline); Player.workPartTime(numCyclesOffline);
} else if (Player.workType === CONSTANTS.WorkTypeGraftAugmentation) {
Player.graftAugmentationWork(numCyclesOffline);
} else { } else {
Player.work(numCyclesOffline); Player.work(numCyclesOffline);
} }
@ -459,4 +467,35 @@ const Engine: {
}, },
}; };
/**
* Shows a toast warning that lets the player know that auto-saves are disabled, with an button to re-enable them.
*/
function warnAutosaveDisabled(): void {
// If the player has suppressed those warnings let's just exit right away.
if (Settings.SuppressAutosaveDisabledWarnings) return;
// We don't want this warning to show up on certain pages.
// When in recovery or importing we want to keep autosave disabled.
const ignoredPages = [Page.Recovery, Page.ImportSave];
if (ignoredPages.includes(Router.page())) return;
const warningToast = (
<>
Auto-saves are <strong>disabled</strong>!
<Button
sx={{ ml: 1 }}
color="warning"
size="small"
onClick={() => {
// We reset the value to a default
Settings.AutosaveInterval = 60;
}}
>
Enable
</Button>
</>
);
SnackbarEvents.emit(warningToast, "warning", 5000);
}
export { Engine }; export { Engine };

@ -42,7 +42,7 @@ import { BladeburnerRoot } from "../Bladeburner/ui/BladeburnerRoot";
import { GangRoot } from "../Gang/ui/GangRoot"; import { GangRoot } from "../Gang/ui/GangRoot";
import { CorporationRoot } from "../Corporation/ui/CorporationRoot"; import { CorporationRoot } from "../Corporation/ui/CorporationRoot";
import { InfiltrationRoot } from "../Infiltration/ui/InfiltrationRoot"; import { InfiltrationRoot } from "../Infiltration/ui/InfiltrationRoot";
import { ResleeveRoot } from "../PersonObjects/Resleeving/ui/ResleeveRoot"; import { GraftingRoot } from "../PersonObjects/Grafting/ui/GraftingRoot";
import { WorkInProgressRoot } from "./WorkInProgressRoot"; import { WorkInProgressRoot } from "./WorkInProgressRoot";
import { GameOptionsRoot } from "./React/GameOptionsRoot"; import { GameOptionsRoot } from "./React/GameOptionsRoot";
import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot"; import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot";
@ -135,7 +135,7 @@ export let Router: IRouter = {
toInfiltration: uninitialized, toInfiltration: uninitialized,
toJob: uninitialized, toJob: uninitialized,
toMilestones: uninitialized, toMilestones: uninitialized,
toResleeves: uninitialized, toGrafting: uninitialized,
toScriptEditor: uninitialized, toScriptEditor: uninitialized,
toSleeves: uninitialized, toSleeves: uninitialized,
toStockMarket: uninitialized, toStockMarket: uninitialized,
@ -226,7 +226,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
toGang: () => setPage(Page.Gang), toGang: () => setPage(Page.Gang),
toHacknetNodes: () => setPage(Page.Hacknet), toHacknetNodes: () => setPage(Page.Hacknet),
toMilestones: () => setPage(Page.Milestones), toMilestones: () => setPage(Page.Milestones),
toResleeves: () => setPage(Page.Resleeves), toGrafting: () => setPage(Page.Grafting),
toScriptEditor: (files: Record<string, string>, options?: ScriptEditorRouteOptions) => { toScriptEditor: (files: Record<string, string>, options?: ScriptEditorRouteOptions) => {
setEditorOptions({ setEditorOptions({
files, files,
@ -429,8 +429,8 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
mainPage = <BladeburnerRoot />; mainPage = <BladeburnerRoot />;
break; break;
} }
case Page.Resleeves: { case Page.Grafting: {
mainPage = <ResleeveRoot />; mainPage = <GraftingRoot />;
break; break;
} }
case Page.Travel: { case Page.Travel: {

@ -143,51 +143,66 @@ function Work(): React.ReactElement {
let details = <></>; let details = <></>;
let header = <></>; let header = <></>;
let innerText = <></>; let innerText = <></>;
if (player.workType === CONSTANTS.WorkTypeCompanyPartTime || player.workType === CONSTANTS.WorkTypeCompany) { switch (player.workType) {
details = ( case CONSTANTS.WorkTypeCompanyPartTime:
<> case CONSTANTS.WorkTypeCompany:
{player.jobs[player.companyName]} at <strong>{player.companyName}</strong> details = (
</> <>
); {player.jobs[player.companyName]} at <strong>{player.companyName}</strong>
header = ( </>
<> );
Working at <strong>{player.companyName}</strong> header = (
</> <>
); Working at <strong>{player.companyName}</strong>
innerText = ( </>
<> );
+<Reputation reputation={player.workRepGained} /> rep innerText = (
</> <>
); +<Reputation reputation={player.workRepGained} /> rep
} else if (player.workType === CONSTANTS.WorkTypeFaction) { </>
details = ( );
<> break;
{player.factionWorkType} for <strong>{player.currentWorkFactionName}</strong> case CONSTANTS.WorkTypeFaction:
</> details = (
); <>
header = ( {player.factionWorkType} for <strong>{player.currentWorkFactionName}</strong>
<> </>
Working for <strong>{player.currentWorkFactionName}</strong> );
</> header = (
); <>
innerText = ( Working for <strong>{player.currentWorkFactionName}</strong>
<> </>
+<Reputation reputation={player.workRepGained} /> rep );
</> innerText = (
); <>
} else if (player.workType === CONSTANTS.WorkTypeStudyClass) { +<Reputation reputation={player.workRepGained} /> rep
details = <>{player.workType}</>; </>
header = <>You are {player.className}</>; );
innerText = <>{convertTimeMsToTimeElapsedString(player.timeWorked)}</>; break;
} else if (player.workType === CONSTANTS.WorkTypeCreateProgram) { case CONSTANTS.WorkTypeStudyClass:
details = <>Coding {player.createProgramName}</>; details = <>{player.workType}</>;
header = <>Creating a program</>; header = <>You are {player.className}</>;
innerText = ( innerText = <>{convertTimeMsToTimeElapsedString(player.timeWorked)}</>;
<> break;
{player.createProgramName}{" "} case CONSTANTS.WorkTypeCreateProgram:
{((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}% details = <>Coding {player.createProgramName}</>;
</> header = <>Creating a program</>;
); innerText = (
<>
{player.createProgramName}{" "}
{((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}%
</>
);
break;
case CONSTANTS.WorkTypeGraftAugmentation:
details = <>Grafting {player.graftAugmentationName}</>;
header = <>Grafting an Augmentation</>;
innerText = (
<>
<strong>{((player.timeWorkedGraftAugmentation / player.timeNeededToCompleteWork) * 100).toFixed(2)}%</strong>{" "}
done
</>
);
} }
return ( return (
@ -465,7 +480,7 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
<Box sx={{ display: "flex", borderTop: `1px solid ${Settings.theme.welllight}` }}> <Box sx={{ display: "flex", borderTop: `1px solid ${Settings.theme.welllight}` }}>
<Box sx={{ display: "flex", flex: 1, justifyContent: "flex-start", alignItems: "center" }}> <Box sx={{ display: "flex", flex: 1, justifyContent: "flex-start", alignItems: "center" }}>
<IconButton aria-label="save game" onClick={save}> <IconButton aria-label="save game" onClick={save}>
<Tooltip title="Save game"> <Tooltip title={Settings.AutosaveInterval !== 0 ? "Save game" : "Save game (auto-saves are disabled!)"}>
<SaveIcon color={Settings.AutosaveInterval !== 0 ? "primary" : "error"} /> <SaveIcon color={Settings.AutosaveInterval !== 0 ? "primary" : "error"} />
</Tooltip> </Tooltip>
</IconButton> </IconButton>

@ -328,6 +328,14 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
tooltip={<>If this is set, there will be no "Game Saved!" toast appearing after an auto-save.</>} tooltip={<>If this is set, there will be no "Game Saved!" toast appearing after an auto-save.</>}
/> />
</ListItem> </ListItem>
<ListItem>
<OptionSwitch
checked={Settings.SuppressAutosaveDisabledWarnings}
onChange={(newValue) => (Settings.SuppressAutosaveDisabledWarnings = newValue)}
text="Suppress Auto-Save Disabled Warning"
tooltip={<>If this is set, there will be no warning triggered when auto-save is disabled (at 0).</>}
/>
</ListItem>
<ListItem> <ListItem>
<OptionSwitch <OptionSwitch
checked={Settings.DisableHotkeys} checked={Settings.DisableHotkeys}

@ -119,7 +119,7 @@ export const logBoxBaseZIndex = 1500;
function LogWindow(props: IProps): React.ReactElement { function LogWindow(props: IProps): React.ReactElement {
const draggableRef = useRef<HTMLDivElement>(null); const draggableRef = useRef<HTMLDivElement>(null);
const rootRef = useRef<Draggable>(null) const rootRef = useRef<Draggable>(null);
const [script, setScript] = useState(props.script); const [script, setScript] = useState(props.script);
const classes = useStyles(); const classes = useStyles();
const container = useRef<HTMLDivElement>(null); const container = useRef<HTMLDivElement>(null);
@ -129,22 +129,22 @@ function LogWindow(props: IProps): React.ReactElement {
setRerender((old) => !old); setRerender((old) => !old);
} }
useEffect( // useEffect(
() => // () =>
WorkerScriptStartStopEventEmitter.subscribe(() => { // WorkerScriptStartStopEventEmitter.subscribe(() => {
setTimeout(() => { // setTimeout(() => {
const server = GetServer(script.server); // const server = GetServer(script.server);
if (server === null) return; // if (server === null) return;
const exisitingScript = findRunningScript(script.filename, script.args, server); // const exisitingScript = findRunningScript(script.filename, script.args, server);
if (exisitingScript) { // if (exisitingScript) {
exisitingScript.logs = script.logs.concat(exisitingScript.logs) // exisitingScript.logs = script.logs.concat(exisitingScript.logs)
setScript(exisitingScript) // setScript(exisitingScript)
} // }
rerender(); // rerender();
}, 100) // }, 100)
}), // }),
[], // [],
); // );
useEffect(() => { useEffect(() => {
updateLayer(); updateLayer();
@ -224,11 +224,8 @@ function LogWindow(props: IProps): React.ReactElement {
const isOnScreen = (node: HTMLDivElement): boolean => { const isOnScreen = (node: HTMLDivElement): boolean => {
const bounds = node.getBoundingClientRect(); const bounds = node.getBoundingClientRect();
return !(bounds.right < 0 || return !(bounds.right < 0 || bounds.bottom < 0 || bounds.left > innerWidth || bounds.top > outerWidth);
bounds.bottom < 0 || };
bounds.left > innerWidth ||
bounds.top > outerWidth);
}
const resetPosition = (): void => { const resetPosition = (): void => {
const node = rootRef?.current; const node = rootRef?.current;
@ -237,14 +234,11 @@ function LogWindow(props: IProps): React.ReactElement {
state.x = 0; state.x = 0;
state.y = 0; state.y = 0;
node.setState(state); node.setState(state);
} };
const boundToBody = (e: any): void | false => { const boundToBody = (e: any): void | false => {
if (e.clientX < 0 || if (e.clientX < 0 || e.clientY < 0 || e.clientX > innerWidth || e.clientY > innerHeight) return false;
e.clientY < 0 || };
e.clientX > innerWidth ||
e.clientY > innerHeight) return false;
}
return ( return (
<Draggable handle=".drag" onDrag={boundToBody} ref={rootRef}> <Draggable handle=".drag" onDrag={boundToBody} ref={rootRef}>

@ -23,7 +23,7 @@ export enum Page {
Job, Job,
Milestones, Milestones,
Options, Options,
Resleeves, Grafting,
Sleeves, Sleeves,
Stats, Stats,
StockMarket, StockMarket,
@ -74,7 +74,7 @@ export interface IRouter {
toInfiltration(location: Location): void; toInfiltration(location: Location): void;
toJob(): void; toJob(): void;
toMilestones(): void; toMilestones(): void;
toResleeves(): void; toGrafting(): void;
toScriptEditor(files?: Record<string, string>, options?: ScriptEditorRouteOptions): void; toScriptEditor(files?: Record<string, string>, options?: ScriptEditorRouteOptions): void;
toSleeves(): void; toSleeves(): void;
toStockMarket(): void; toStockMarket(): void;

@ -41,8 +41,8 @@ export function WorkInProgressRoot(): React.ReactElement {
return ( return (
<> <>
<Typography variant="h4" color="primary"> <Typography variant="h4" color="primary">
You have not joined {player.currentWorkFactionName || "(Faction not found)"} yet or cannot work at this time, You have not joined {player.currentWorkFactionName || "(Faction not found)"} yet or cannot work at this
please try again if you think this should have worked time, please try again if you think this should have worked
</Typography> </Typography>
<Button onClick={() => router.toFactions()}>Back to Factions</Button> <Button onClick={() => router.toFactions()}>Back to Factions</Button>
</> </>
@ -483,6 +483,42 @@ export function WorkInProgressRoot(): React.ReactElement {
); );
} }
if (player.graftAugmentationName !== "") {
function cancel(): void {
player.finishGraftAugmentationWork(true);
router.toTerminal();
}
function unfocus(): void {
router.toTerminal();
player.stopFocusing();
}
return (
<Grid container direction="column" justifyContent="center" alignItems="center" style={{ minHeight: "100vh" }}>
<Grid item>
<Typography>
You are currently working on crafting {player.graftAugmentationName}.
<br />
<br />
You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}
<br />
<br />
The augmentation is{" "}
{((player.timeWorkedGraftAugmentation / player.timeNeededToCompleteWork) * 100).toFixed(2)}% done being
crafted.
<br />
If you cancel, your work will <b>not</b> be saved, and the money you spent will <b>not</b> be returned.
</Typography>
</Grid>
<Grid item>
<Button sx={{ mx: 2 }} onClick={cancel}>
Cancel work on crafting Augmentation
</Button>
<Button onClick={unfocus}>Do something else simultaneously</Button>
</Grid>
</Grid>
);
}
if (!player.workType) router.toTerminal(); if (!player.workType) router.toTerminal();
return <></>; return <></>;

@ -16,7 +16,6 @@ import "numeral/locales/ru";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
const extraFormats = [1e15, 1e18, 1e21, 1e24, 1e27, 1e30]; const extraFormats = [1e15, 1e18, 1e21, 1e24, 1e27, 1e30];
const extraNotations = ["q", "Q", "s", "S", "o", "n"]; const extraNotations = ["q", "Q", "s", "S", "o", "n"];
const gigaMultiplier = { standard: 1e9, iec60027_2: 2 ** 30 }; const gigaMultiplier = { standard: 1e9, iec60027_2: 2 ** 30 };
@ -52,7 +51,7 @@ class NumeralFormatter {
} }
formatBigNumber(n: number): string { formatBigNumber(n: number): string {
return this.format(n, "0.[000]a"); return this.format(n, "0.000a");
} }
// TODO: leverage numeral.js to do it. This function also implies you can // TODO: leverage numeral.js to do it. This function also implies you can
@ -67,10 +66,10 @@ class NumeralFormatter {
} }
} }
if (nAbs < 1000) { if (nAbs < 1000) {
return this.format(n, "0.[" + "0".repeat(decimalPlaces) + "]"); return this.format(n, "0." + "0".repeat(decimalPlaces));
} }
const str = this.format(n, "0.[" + "0".repeat(decimalPlaces) + "]a"); const str = this.format(n, "0." + "0".repeat(decimalPlaces) + "a");
if (str === "NaNt") return this.format(n, "0.[" + " ".repeat(decimalPlaces) + "]e+0"); if (str === "NaNt") return this.format(n, "0." + " ".repeat(decimalPlaces) + "e+0");
return str; return str;
} }
@ -113,8 +112,7 @@ class NumeralFormatter {
} }
formatRAM(n: number): string { formatRAM(n: number): string {
if(Settings.UseIEC60027_2) if (Settings.UseIEC60027_2) {
{
return this.format(n * gigaMultiplier.iec60027_2, "0.00ib"); return this.format(n * gigaMultiplier.iec60027_2, "0.00ib");
} }
return this.format(n * gigaMultiplier.standard, "0.00b"); return this.format(n * gigaMultiplier.standard, "0.00b");
@ -189,7 +187,7 @@ class NumeralFormatter {
} }
parseCustomLargeNumber(str: string): number { parseCustomLargeNumber(str: string): number {
const numericRegExp = new RegExp('^(\-?\\d+\\.?\\d*)([' + extraNotations.join("") + ']?)$'); const numericRegExp = new RegExp("^(-?\\d+\\.?\\d*)([" + extraNotations.join("") + "]?)$");
const match = str.match(numericRegExp); const match = str.match(numericRegExp);
if (match == null) { if (match == null) {
return NaN; return NaN;
@ -203,14 +201,17 @@ class NumeralFormatter {
} }
largestAbsoluteNumber(n1: number, n2 = 0, n3 = 0): number { largestAbsoluteNumber(n1: number, n2 = 0, n3 = 0): number {
if(isNaN(n1)) n1=0; if (isNaN(n1)) n1 = 0;
if(isNaN(n2)) n2=0; if (isNaN(n2)) n2 = 0;
if(isNaN(n3)) n3=0; if (isNaN(n3)) n3 = 0;
const largestAbsolute = Math.max(Math.abs(n1), Math.abs(n2), Math.abs(n3)); const largestAbsolute = Math.max(Math.abs(n1), Math.abs(n2), Math.abs(n3));
switch(largestAbsolute) { switch (largestAbsolute) {
case Math.abs(n1): return n1; case Math.abs(n1):
case Math.abs(n2): return n2; return n1;
case Math.abs(n3): return n3; case Math.abs(n2):
return n2;
case Math.abs(n3):
return n3;
} }
return 0; return 0;
} }
@ -222,21 +223,29 @@ class NumeralFormatter {
const parsed = parseFloat(s); const parsed = parseFloat(s);
const selfParsed = this.parseCustomLargeNumber(s); const selfParsed = this.parseCustomLargeNumber(s);
// Check for one or more NaN values // Check for one or more NaN values
if (isNaN(parsed) && numeralValue === null && isNaN(selfParsed)) { // 3x NaN if (isNaN(parsed) && numeralValue === null && isNaN(selfParsed)) {
// 3x NaN
return NaN; return NaN;
} else if (isNaN(parsed) && isNaN(selfParsed)) { // 2x NaN } else if (isNaN(parsed) && isNaN(selfParsed)) {
// 2x NaN
return numeralValue; return numeralValue;
} else if (numeralValue === null && isNaN(selfParsed)) { // 2x NaN } else if (numeralValue === null && isNaN(selfParsed)) {
// 2x NaN
return parsed; return parsed;
} else if (isNaN(parsed) && numeralValue === null) { // 2x NaN } else if (isNaN(parsed) && numeralValue === null) {
// 2x NaN
return selfParsed; return selfParsed;
} else if (isNaN(parsed)) { // 1x NaN } else if (isNaN(parsed)) {
// 1x NaN
return this.largestAbsoluteNumber(numeralValue, selfParsed); return this.largestAbsoluteNumber(numeralValue, selfParsed);
} else if (numeralValue === null) { // 1x NaN } else if (numeralValue === null) {
// 1x NaN
return this.largestAbsoluteNumber(parsed, selfParsed); return this.largestAbsoluteNumber(parsed, selfParsed);
} else if (isNaN(selfParsed)) { // 1x NaN } else if (isNaN(selfParsed)) {
// 1x NaN
return this.largestAbsoluteNumber(numeralValue, parsed); return this.largestAbsoluteNumber(numeralValue, parsed);
} else { // no NaN } else {
// no NaN
return this.largestAbsoluteNumber(numeralValue, parsed, selfParsed); return this.largestAbsoluteNumber(numeralValue, parsed, selfParsed);
} }
} }