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;
/**
* 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;
}
/**
* 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
* @public
@ -2582,6 +2635,13 @@ export declare interface NS extends Singularity {
*/
readonly ui: UserInterface;
/**
* Namespace for grafting functions.
* @remarks
* RAM cost: 0 GB
*/
readonly grafting: Grafting;
/**
* Arguments passed into the script.
*
@ -4833,6 +4893,7 @@ export declare interface Player {
tor: boolean;
hasCorporation: 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
* 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;
@ -5780,6 +5841,67 @@ export declare interface Singularity {
* @returns True if the focus was changed.
*/
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
* @returns The width of the gift.
*/
width(): number;
giftWidth(): number;
/**
* Stanek's Gift height.
* @remarks
* RAM cost: 0.4 GB
* @returns The height of the gift.
*/
height(): number;
giftHeight(): number;
/**
* 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.
* @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.
@ -6156,7 +6278,7 @@ export declare interface Stanek {
* @remarks
* RAM cost: 0 GB
*/
clear(): void;
clearGift(): void;
/**
* 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.
* @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.
* @remarks
@ -6181,7 +6303,7 @@ export declare interface Stanek {
* @param fragmentId - ID of the fragment to place.
* @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.
* @remarks
@ -6191,7 +6313,7 @@ export declare interface Stanek {
* @param rootY - Y against which to align the top left of the fragment.
* @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.
@ -6202,7 +6324,7 @@ export declare interface Stanek {
* @param rootY - Y against which to align the top left of the fragment.
* @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.
*/
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>
Intelligence <advancedgameplay/intelligence>
Sleeves <advancedgameplay/sleeves>
Grafting <advancedgameplay/grafting>
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
* Bladeburner
* 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.
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. |
|| || * 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 |
|| || company by 1% per favor (rather than just the reputation gain). |

@ -3,6 +3,108 @@
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
--------------------------------
@ -21,7 +123,7 @@ v1.5.0 - Steam Cloud integration
* Fix display of corporation product equation (@SagePtr)
* Make Bitverse more accessible (@ChrissiQ)
* Make corporation warehouse more accessible (@ChrissiQ)
* Make tab style more consistent (@nikfolas)
* Make tab style more consistent (@nickofolas)
** Netscript **

@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
# built documents.
#
# The short X.Y version.
version = '1.5'
version = '1.6'
# 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
# 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 |
| [expandCity(divisionName, cityName)](./bitburner.corporation.expandcity.md) | Expand to a new city |
| [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 |
| [getDivision(divisionName)](./bitburner.corporation.getdivision.md) | Get division data |
| [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) | |
| [GangTaskStats](./bitburner.gangtaskstats.md) | Object representing data representing a gang member task. |
| [GangTerritory](./bitburner.gangterritory.md) | |
| [Grafting](./bitburner.grafting.md) | Grafting API |
| [HackingFormulas](./bitburner.hackingformulas.md) | Hacking formulas |
| [HackingMultipliers](./bitburner.hackingmultipliers.md) | Hack related multipliers. |
| [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 |
| [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. |
| [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. |
| [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 |

@ -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\_mult](./bitburner.player.dexterity_mult.md) | number | |
| [dexterity](./bitburner.player.dexterity.md) | number | |
| [entropy](./bitburner.player.entropy.md) | number | |
| [faction\_rep\_mult](./bitburner.player.faction_rep_mult.md) | number | |
| [factions](./bitburner.player.factions.md) | string\[\] | |
| [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. |
| [getCrimeStats(crime)](./bitburner.singularity.getcrimestats.md) | Get stats related to a crime. |
| [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. |
| [getFactionFavorGain(faction)](./bitburner.singularity.getfactionfavorgain.md) | Get faction favor gain. |
| [getFactionRep(faction)](./bitburner.singularity.getfactionrep.md) | Get faction reputation. |

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

@ -1,15 +1,15 @@
<!-- 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.
<b>Signature:</b>
```typescript
canPlace(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
canPlaceFragment(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
```
## Parameters

@ -1,15 +1,15 @@
<!-- 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.
<b>Signature:</b>
```typescript
charge(rootX: number, rootY: number): Promise<void>;
chargeFragment(rootX: number, rootY: number): Promise<void>;
```
## Parameters

@ -1,15 +1,15 @@
<!-- 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.
<b>Signature:</b>
```typescript
clear(): void;
clearGift(): void;
```
<b>Returns:</b>

@ -1,15 +1,15 @@
<!-- 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.
<b>Signature:</b>
```typescript
get(rootX: number, rootY: number): ActiveFragment | undefined;
getFragment(rootX: number, rootY: number): ActiveFragment | undefined;
```
## Parameters

@ -1,15 +1,15 @@
<!-- 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.
<b>Signature:</b>
```typescript
height(): number;
giftHeight(): number;
```
<b>Returns:</b>

@ -1,15 +1,15 @@
<!-- 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.
<b>Signature:</b>
```typescript
width(): number;
giftWidth(): number;
```
<b>Returns:</b>

@ -17,13 +17,13 @@ interface Stanek
| Method | Description |
| --- | --- |
| [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. |
| [charge(rootX, rootY)](./bitburner.stanek.charge.md) | Charge a fragment, increasing its power. |
| [clear()](./bitburner.stanek.clear.md) | Clear the board of all fragments. |
| [canPlaceFragment(rootX, rootY, rotation, fragmentId)](./bitburner.stanek.canplacefragment.md) | Check if fragment can be placed at specified location. |
| [chargeFragment(rootX, rootY)](./bitburner.stanek.chargefragment.md) | Charge a fragment, increasing its power. |
| [clearGift()](./bitburner.stanek.cleargift.md) | Clear the board of all fragments. |
| [fragmentDefinitions()](./bitburner.stanek.fragmentdefinitions.md) | List possible fragments. |
| [get(rootX, rootY)](./bitburner.stanek.get.md) | Get placed fragment at location. |
| [height()](./bitburner.stanek.height.md) | Stanek's Gift height. |
| [place(rootX, rootY, rotation, fragmentId)](./bitburner.stanek.place.md) | Place fragment on Stanek's Gift. |
| [remove(rootX, rootY)](./bitburner.stanek.remove.md) | Remove fragment at location. |
| [width()](./bitburner.stanek.width.md) | Stanek's Gift width. |
| [getFragment(rootX, rootY)](./bitburner.stanek.getfragment.md) | Get placed fragment at location. |
| [giftHeight()](./bitburner.stanek.giftheight.md) | Stanek's Gift height. |
| [giftWidth()](./bitburner.stanek.giftwidth.md) | Stanek's Gift width. |
| [placeFragment(rootX, rootY, rotation, fragmentId)](./bitburner.stanek.placefragment.md) | Place fragment on Stanek's Gift. |
| [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. -->
[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.
<b>Signature:</b>
```typescript
place(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
placeFragment(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
```
## Parameters

@ -1,15 +1,15 @@
<!-- 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.
<b>Signature:</b>
```typescript
remove(rootX: number, rootY: number): boolean;
removeFragment(rootX: number, rootY: number): boolean;
```
## Parameters

@ -32,6 +32,8 @@ export interface TIX
| [placeOrder(sym, shares, price, type, pos)](./bitburner.tix.placeorder.md) | Place order for stocks. |
| [purchase4SMarketData()](./bitburner.tix.purchase4smarketdata.md) | Purchase 4S Market Data 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. |
| [sellShort(sym, shares)](./bitburner.tix.sellshort.md) | Sell short stock. |
| [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",
"license": "SEE LICENSE IN license.txt",
"version": "1.5.0",
"version": "1.6.0",
"main": "electron-main.js",
"author": {
"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-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",
"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 Tooltip from "@mui/material/Tooltip";
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 {
const setRerender = useState(true)[1];
@ -55,6 +59,38 @@ export function InstalledAugmentations(): React.ReactElement {
</Button>
</Tooltip>
<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) => {
const aug = Augmentations[e.name];

@ -425,7 +425,7 @@ BitNodes["BitNode10"] = new BitNode(
This BitNode unlocks Sleeve technology. Sleeve technology allows you to:
<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 />
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks
synchronously

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

@ -41,6 +41,7 @@ export const CONSTANTS: {
IntelligenceInfiltrationWeight: number;
IntelligenceCrimeBaseExpGain: number;
IntelligenceProgramBaseExpGain: number;
IntelligenceGraftBaseExpGain: number;
IntelligenceTerminalHackBaseExpGain: number;
IntelligenceSingFnBaseExpGain: number;
IntelligenceClassBaseExpGain: number;
@ -71,6 +72,7 @@ export const CONSTANTS: {
WorkTypeCreateProgram: string;
WorkTypeStudyClass: string;
WorkTypeCrime: string;
WorkTypeGraftAugmentation: string;
ClassStudyComputerScience: string;
ClassDataStructures: string;
ClassNetworks: string;
@ -108,11 +110,14 @@ export const CONSTANTS: {
CodingContractBaseFactionRepGain: number;
CodingContractBaseCompanyRepGain: number;
CodingContractBaseMoneyGain: number;
AugmentationGraftingCostMult: number;
AugmentationGraftingTimeBase: number;
EntropyEffect: number;
TotalNumBitNodes: number;
LatestUpdate: string;
} = {
VersionString: "1.5.0",
VersionNumber: 11,
VersionString: "1.6.0",
VersionNumber: 12,
// Speed (in ms) at which the main loop is updated
_idleSpeed: 200,
@ -180,6 +185,7 @@ export const CONSTANTS: {
IntelligenceInfiltrationWeight: 0.1, // Weight for how much int affects infiltration success rates
IntelligenceCrimeBaseExpGain: 0.05,
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
IntelligenceSingFnBaseExpGain: 1.5,
IntelligenceClassBaseExpGain: 0.01,
@ -224,6 +230,7 @@ export const CONSTANTS: {
WorkTypeCreateProgram: "Working on Create a Program",
WorkTypeStudyClass: "Studying or Taking a class at university",
WorkTypeCrime: "Committing a crime",
WorkTypeGraftAugmentation: "Grafting an Augmentation",
ClassStudyComputerScience: "studying Computer Science",
ClassDataStructures: "taking a Data Structures course",
@ -269,68 +276,116 @@ export const CONSTANTS: {
CodingContractBaseCompanyRepGain: 4000,
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
TotalNumBitNodes: 24,
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 **
* background now matches game primary color (@nickofolas)
* page title contains version (@MartinFourier)
* Major text editor improvements (@nickofolas)
* Display bonus time on sleeve page (@MartinFourier)
* Several UI improvements (@nickofolas, @smolgumball, @DrCuriosity, @phyzical)
* Fix aug display in alpha (@Dominik Winter)
* Fix display of corporation product equation (@SagePtr)
* Make Bitverse more accessible (@ChrissiQ)
* Make corporation warehouse more accessible (@ChrissiQ)
* Make tab style more consistent (@nikfolas)
* 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)
** Netscript **
** API **
* Fix bug with async.
* Add 'printf' ns function (@Ninetailed)
* Remove blob caching.
* Fix formulas access check (@Ornedan)
* Fix bug in exp calculation (@qcorradi)
* Fix NaN comparison (@qcorradi)
* Fix travelToCity with bad argument (@SlyCedix)
* Fix bug where augs could not be purchased via sing (@reacocard)
* Fix rounding error in donateToFaction (@Risenafis)
* Fix bug with weakenAnalyze (@rhobes)
* Prevent exploit with atExit (@Ornedan)
* Double 'share' power
* 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)
** Corporations **
** Arcade **
* Fix bugs with corp API (@pigalot)
* Add smart supply func to corp API (@pd)
* Added an arcade to New Tokyo where you can play a 4 year old version of bitburner.
** Misc. **
* The file API now allows GET and DELETE (@lordducky)
* Force achievement calculation on BN completion (@SagePtr)
* Cleanup in repository (@MartinFourier)
* Several improvements to the electron version (@MartinFourier)
* Fix bug with casino roulette (@jamie-mac)
* Terminal history persists in savefile (@MartinFourier)
* Fix tests (@jamie-mac)
* Fix crash with electron windows tracker (@smolgumball)
* Fix BN6/7 passive reputation gain (@BrianLDev)
* Fix Sleeve not resetting on install (@waffleattack)
* Sort joined factions (@jjayeon)
* Update documentation / typo (@lethern, @Meowdoleon, @JohnnyUrosevic, @JosephDavidTalbot,
@pd, @lethern, @lordducky, @zeddrak, @fearnlj01, @reasonablytall, @MatthewTh0,
@SagePtr, @manniL, @Jedimaster4559, @loganville, @Arrow2thekn33, @wdpk, @fwolfst,
@fschoenfeldt, @Waladil, @AdamTReineke, @citrusmunch, @factubsio, @ashtongreen,
@ChrissiQ, @DJ-Laser, @waffleattack, @ApamNapat, @CrafterKolyan, @DSteve595)
* 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.
`,
};

@ -23,6 +23,7 @@ import { Sleeves } from "./DevMenu/ui/Sleeves";
import { Stanek } from "./DevMenu/ui/Stanek";
import { TimeSkip } from "./DevMenu/ui/TimeSkip";
import { Achievements } from "./DevMenu/ui/Achievements";
import { Entropy } from "./DevMenu/ui/Entropy";
import Typography from "@mui/material/Typography";
import { Exploit } from "./Exploits/Exploit";
@ -63,6 +64,7 @@ export function DevMenuRoot(props: IProps): React.ReactElement {
<TimeSkip 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[] {
if (isPlayersGang) {
const augs: string[] = [];
for (const augName of Object.keys(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
const aug = Augmentations[augName];
if (!aug.isSpecial) {
augs.push(augName);
}
let augs = Object.values(Augmentations);
// Remove special augs.
augs = augs.filter((a) => !a.isSpecial);
if (player.bitNodeN !== 2) {
// 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 {
return props.faction.augmentations.slice();
}

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

@ -227,7 +227,6 @@ export class Gang implements IGang {
AllGangs[thisGang].territory += territoryGain;
if (AllGangs[thisGang].territory > 0.999) AllGangs[thisGang].territory = 1;
AllGangs[otherGang].territory -= territoryGain;
if (AllGangs[thisGang].territory < 0.001) AllGangs[thisGang].territory = 0;
if (thisGang === gangName) {
this.clash(true); // Player won
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
*/
function handleResleeving(): void {
router.toResleeves();
function handleGrafting(): void {
router.toGrafting();
}
function renderBladeburner(): React.ReactElement {
@ -151,11 +151,11 @@ export function SpecialLocation(props: IProps): React.ReactElement {
);
}
function renderResleeving(): React.ReactElement {
if (!player.canAccessResleeving()) {
function renderGrafting(): React.ReactElement {
if (!player.canAccessGrafting()) {
return <></>;
}
return <Button onClick={handleResleeving}>Re-Sleeve</Button>;
return <Button onClick={handleGrafting} sx={{ my: 5 }}>Enter the secret lab</Button>;
}
function handleCotMG(): void {
@ -299,7 +299,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
switch (props.loc.name) {
case LocationName.NewTokyoVitaLife: {
return renderResleeving();
return renderGrafting();
}
case LocationName.Sector12CityHall: {
return <CreateCorporation />;

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

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

@ -1,5 +1,5 @@
import { FactionNames } from '../Faction/data/FactionNames';
import { GangConstants } from '../Gang/data/Constants';
import { FactionNames } from "../Faction/data/FactionNames";
import { GangConstants } from "../Gang/data/Constants";
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
@ -48,7 +48,8 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
};
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"));
// this list is copied from Faction/ui/Root.tsx
@ -101,12 +102,13 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
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"));
checkGangApiAccess("getMemberInformation");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("getMemberInformation", name);
const member = getGangMember("getMemberInformation", memberName);
return {
name: member.name,
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");
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"));
checkGangApiAccess("recruitMember");
const gang = player.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) {
workerScript.log("gang.recruitMember", () => `Successfully recruited Gang Member '${name}'`);
workerScript.log("gang.recruitMember", () => `Successfully recruited Gang Member '${memberName}'`);
} else {
workerScript.log("gang.recruitMember", () => `Failed to recruit Gang Member '${name}'`);
workerScript.log("gang.recruitMember", () => `Failed to recruit Gang Member '${memberName}'`);
}
return recruited;
@ -184,7 +187,9 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
tasks.unshift("Unassigned");
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"));
checkGangApiAccess("setMemberTask");
const member = getGangMember("setMemberTask", memberName);
@ -193,9 +198,10 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
if (!gang.getAllTaskNames().includes(taskName)) {
workerScript.log(
"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);
if (success) {
@ -212,7 +218,8 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
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"));
checkGangApiAccess("getTaskStats");
const task = getGangTask("getTaskStats", taskName);
@ -225,7 +232,8 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
checkGangApiAccess("getEquipmentNames");
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"));
checkGangApiAccess("getEquipmentCost");
const gang = player.gang;
@ -234,14 +242,16 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
if (upg === null) return Infinity;
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"));
checkGangApiAccess("getEquipmentType");
const upg = GangMemberUpgrades[equipName];
if (upg == null) return "";
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"));
checkGangApiAccess("getEquipmentStats");
const equipment = GangMemberUpgrades[equipName];
@ -251,7 +261,9 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
const typecheck: EquipmentStats = equipment.mults;
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"));
checkGangApiAccess("purchaseEquipment");
const gang = player.gang;
@ -271,28 +283,31 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
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"));
checkGangApiAccess("ascendMember");
const gang = player.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;
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"));
checkGangApiAccess("getAscensionResult");
const gang = player.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;
return {
respect: member.earnedRespect,
...member.getAscensionResults(),
};
},
setTerritoryWarfare: function (engage: any): void {
setTerritoryWarfare: function (_engage: unknown): void {
const engage = helper.boolean(_engage);
helper.updateDynamicRam("setTerritoryWarfare", getRamCost(player, "gang", "setTerritoryWarfare"));
checkGangApiAccess("setTerritoryWarfare");
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");
}
},
getChanceToWinClash: function (otherGang: any): number {
getChanceToWinClash: function (_otherGang: unknown): number {
const otherGang = helper.string("getChanceToWinClash", "otherGang", _otherGang);
helper.updateDynamicRam("getChanceToWinClash", getRamCost(player, "gang", "getChanceToWinClash"));
checkGangApiAccess("getChanceToWinClash");
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 { 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 { CompanyPosition } from "../Company/CompanyPosition";
@ -49,7 +55,7 @@ export function NetscriptSingularity(
workerScript: WorkerScript,
helper: INetscriptHelper,
): ISingularity {
const getAugmentation = function (func: any, name: any): Augmentation {
const getAugmentation = function (func: string, name: string): Augmentation {
if (!augmentationExists(name)) {
throw helper.makeRuntimeErrorMsg(func, `Invalid augmentation: '${name}'`);
}
@ -57,7 +63,7 @@ export function NetscriptSingularity(
return Augmentations[name];
};
const getFaction = function (func: any, name: any): Faction {
const getFaction = function (func: string, name: string): Faction {
if (!factionExists(name)) {
throw helper.makeRuntimeErrorMsg(func, `Invalid faction name: '${name}`);
}
@ -65,7 +71,7 @@ export function NetscriptSingularity(
return Factions[name];
};
const getCompany = function (func: any, name: any): Company {
const getCompany = function (func: string, name: string): Company {
const company = Companies[name];
if (company == null || !(company instanceof Company)) {
throw helper.makeRuntimeErrorMsg(func, `Invalid company name: '${name}'`);
@ -73,9 +79,9 @@ export function NetscriptSingularity(
return company;
};
const runAfterReset = function (cbScript = null): void {
const runAfterReset = function (cbScript: string | null = null): void {
//Run a script after reset
if (cbScript && isString(cbScript)) {
if (!cbScript) return;
const home = player.getHomeComputer();
for (const script of home.scripts) {
if (script.filename === cbScript) {
@ -89,10 +95,10 @@ export function NetscriptSingularity(
startWorkerScript(player, runningScriptObj, home);
}
}
}
};
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.checkSingularityAccess("getOwnedAugmentations");
const res = [];
@ -106,82 +112,99 @@ export function NetscriptSingularity(
}
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.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.hasGangWith(facname)) {
const res = [];
for (const augName of Object.keys(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
const aug = Augmentations[augName];
if (!aug.isSpecial) {
res.push(augName);
}
if (player.hasGangWith(facName)) {
let augs = Object.values(Augmentations);
// Remove special augs.
augs = augs.filter((a) => !a.isSpecial);
if (player.bitNodeN !== 2) {
// 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();
},
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.checkSingularityAccess("getAugmentationCost");
const aug = getAugmentation("getAugmentationCost", name);
const aug = getAugmentation("getAugmentationCost", augName);
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.checkSingularityAccess("getAugmentationPrereq");
const aug = getAugmentation("getAugmentationPrereq", name);
const aug = getAugmentation("getAugmentationPrereq", augName);
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.checkSingularityAccess("getAugmentationPrice");
const aug = getAugmentation("getAugmentationPrice", name);
const aug = getAugmentation("getAugmentationPrice", augName);
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.checkSingularityAccess("getAugmentationRepReq");
const aug = getAugmentation("getAugmentationRepReq", name);
const aug = getAugmentation("getAugmentationRepReq", augName);
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.checkSingularityAccess("getAugmentationStats");
const aug = getAugmentation("getAugmentationStats", name);
const aug = getAugmentation("getAugmentationStats", augName);
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.checkSingularityAccess("purchaseAugmentation");
const fac = getFaction("purchaseAugmentation", faction);
const aug = getAugmentation("purchaseAugmentation", name);
const fac = getFaction("purchaseAugmentation", facName);
const aug = getAugmentation("purchaseAugmentation", augName);
let augs = [];
if (player.hasGangWith(faction)) {
if (player.hasGangWith(facName)) {
for (const augName of Object.keys(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
const tempAug = Augmentations[augName];
if (!tempAug.isSpecial) {
const aug = Augmentations[augName];
if (
augName === AugmentationNames.NeuroFluxGovernor ||
(augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) ||
// 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 {
augs = fac.augmentations;
}
if (!augs.includes(name)) {
if (!augs.includes(augName)) {
workerScript.log(
"purchaseAugmentation",
() => `Faction '${faction}' does not have the '${name}' augmentation.`,
() => `Faction '${facName}' does not have the '${augName}' augmentation.`,
);
return false;
}
@ -190,13 +213,13 @@ export function NetscriptSingularity(
if (!isNeuroflux) {
for (let j = 0; j < player.queuedAugmentations.length; ++j) {
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;
}
}
for (let j = 0; j < player.augmentations.length; ++j) {
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;
}
}
@ -216,7 +239,8 @@ export function NetscriptSingularity(
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.checkSingularityAccess("softReset");
@ -230,7 +254,8 @@ export function NetscriptSingularity(
workerScript.running = false;
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.checkSingularityAccess("installAugmentations");
@ -250,9 +275,11 @@ export function NetscriptSingularity(
workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
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.checkSingularityAccess("goToLocation");
const location = Object.values(Locations).find((l) => l.name === locationName);
@ -268,7 +295,10 @@ export function NetscriptSingularity(
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50000);
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.checkSingularityAccess("universityCourse");
const wasFocusing = player.focus;
@ -356,7 +386,10 @@ export function NetscriptSingularity(
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.checkSingularityAccess("gymWorkout");
const wasFocusing = player.focus;
@ -468,11 +501,12 @@ export function NetscriptSingularity(
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.checkSingularityAccess("travelToCity");
switch (cityname) {
switch (cityName) {
case CityName.Aevum:
case CityName.Chongqing:
case CityName.Sector12:
@ -484,22 +518,22 @@ export function NetscriptSingularity(
return false;
}
player.loseMoney(CONSTANTS.TravelCost, "other");
player.city = cityname;
workerScript.log("travelToCity", () => `Traveled to ${cityname}`);
player.city = cityName;
workerScript.log("travelToCity", () => `Traveled to ${cityName}`);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50000);
return true;
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.checkSingularityAccess("purchaseTor");
if (player.hasTorRouter()) {
workerScript.log("purchaseTor", () => "You already have a TOR router!");
return false;
return true;
}
if (player.money < CONSTANTS.TorRouterCost) {
@ -525,7 +559,8 @@ export function NetscriptSingularity(
workerScript.log("purchaseTor", () => "You have purchased a Tor router!");
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.checkSingularityAccess("purchaseProgram");
@ -534,8 +569,6 @@ export function NetscriptSingularity(
return false;
}
programName = programName.toLowerCase();
const item = Object.values(DarkWebItems).find((i) => i.program.toLowerCase() === programName);
if (item == null) {
workerScript.log("purchaseProgram", () => `Invalid program name: '${programName}.`);
@ -564,12 +597,13 @@ export function NetscriptSingularity(
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 5000);
return true;
},
getCurrentServer: function (): any {
getCurrentServer: function (): string {
helper.updateDynamicRam("getCurrentServer", getRamCost(player, "getCurrentServer"));
helper.checkSingularityAccess("getCurrentServer");
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.checkSingularityAccess("connect");
if (!hostname) {
@ -604,13 +638,13 @@ export function NetscriptSingularity(
return false;
},
manualHack: function (): any {
manualHack: function (): Promise<number> {
helper.updateDynamicRam("manualHack", getRamCost(player, "manualHack"));
helper.checkSingularityAccess("manualHack");
const server = player.getCurrentServer();
return helper.hack(server.hostname, true);
},
installBackdoor: function (): any {
installBackdoor: function (): Promise<void> {
helper.updateDynamicRam("installBackdoor", getRamCost(player, "installBackdoor"));
helper.checkSingularityAccess("installBackdoor");
const baseserver = player.getCurrentServer();
@ -648,8 +682,8 @@ export function NetscriptSingularity(
helper.checkSingularityAccess("isFocused");
return player.focus;
},
setFocus: function (afocus: any): boolean {
const focus = helper.boolean(afocus);
setFocus: function (_focus: unknown): boolean {
const focus = helper.boolean(_focus);
helper.updateDynamicRam("setFocus", getRamCost(player, "setFocus"));
helper.checkSingularityAccess("setFocus");
if (!player.isWorking) {
@ -677,7 +711,7 @@ export function NetscriptSingularity(
}
return false;
},
getStats: function (): any {
getStats: function (): PlayerSkills {
helper.updateDynamicRam("getStats", getRamCost(player, "getStats"));
helper.checkSingularityAccess("getStats");
workerScript.log("getStats", () => `getStats is deprecated, please use getplayer`);
@ -692,7 +726,7 @@ export function NetscriptSingularity(
intelligence: player.intelligence,
};
},
getCharacterInformation: function (): any {
getCharacterInformation: function (): CharacterInfo {
helper.updateDynamicRam("getCharacterInformation", getRamCost(player, "getCharacterInformation"));
helper.checkSingularityAccess("getCharacterInformation");
workerScript.log("getCharacterInformation", () => `getCharacterInformation is deprecated, please use getplayer`);
@ -708,6 +742,8 @@ export function NetscriptSingularity(
mult: {
agility: player.agility_mult,
agilityExp: player.agility_exp_mult,
charisma: player.charisma,
charismaExp: player.charisma_exp,
companyRep: player.company_rep_mult,
crimeMoney: player.crime_money_mult,
crimeSuccess: player.crime_success_mult,
@ -740,21 +776,21 @@ export function NetscriptSingularity(
charismaExp: player.charisma_exp,
};
},
hospitalize: function (): any {
hospitalize: function (): void {
helper.updateDynamicRam("hospitalize", getRamCost(player, "hospitalize"));
helper.checkSingularityAccess("hospitalize");
if (player.isWorking || Router.page() === Page.Infiltration || Router.page() === Page.BitVerse) {
workerScript.log("hospitalize", () => "Cannot go to the hospital because the player is busy.");
return;
}
return player.hospitalize();
player.hospitalize();
},
isBusy: function (): any {
isBusy: function (): boolean {
helper.updateDynamicRam("isBusy", getRamCost(player, "isBusy"));
helper.checkSingularityAccess("isBusy");
return player.isWorking || Router.page() === Page.Infiltration || Router.page() === Page.BitVerse;
},
stopAction: function (): any {
stopAction: function (): boolean {
helper.updateDynamicRam("stopAction", getRamCost(player, "stopAction"));
helper.checkSingularityAccess("stopAction");
if (player.isWorking) {
@ -768,7 +804,7 @@ export function NetscriptSingularity(
}
return false;
},
upgradeHomeCores: function (): any {
upgradeHomeCores: function (): boolean {
helper.updateDynamicRam("upgradeHomeCores", getRamCost(player, "upgradeHomeCores"));
helper.checkSingularityAccess("upgradeHomeCores");
@ -798,13 +834,13 @@ export function NetscriptSingularity(
);
return true;
},
getUpgradeHomeCoresCost: function (): any {
getUpgradeHomeCoresCost: function (): number {
helper.updateDynamicRam("getUpgradeHomeCoresCost", getRamCost(player, "getUpgradeHomeCoresCost"));
helper.checkSingularityAccess("getUpgradeHomeCoresCost");
return player.getUpgradeHomeCoresCost();
},
upgradeHomeRam: function (): any {
upgradeHomeRam: function (): boolean {
helper.updateDynamicRam("upgradeHomeRam", getRamCost(player, "upgradeHomeRam"));
helper.checkSingularityAccess("upgradeHomeRam");
@ -837,13 +873,15 @@ export function NetscriptSingularity(
);
return true;
},
getUpgradeHomeRamCost: function (): any {
getUpgradeHomeRamCost: function (): number {
helper.updateDynamicRam("getUpgradeHomeRamCost", getRamCost(player, "getUpgradeHomeRamCost"));
helper.checkSingularityAccess("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.checkSingularityAccess("workForCompany");
@ -897,12 +935,14 @@ export function NetscriptSingularity(
);
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.checkSingularityAccess("applyToCompany");
getCompany("applyToCompany", companyName);
player.location = companyName;
player.location = companyName as LocationName;
let res;
switch (field.toLowerCase()) {
case "software":
@ -967,66 +1007,73 @@ export function NetscriptSingularity(
}
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.checkSingularityAccess("getCompanyRep");
const company = getCompany("getCompanyRep", companyName);
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.checkSingularityAccess("getCompanyFavor");
const company = getCompany("getCompanyFavor", companyName);
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.checkSingularityAccess("getCompanyFavorGain");
const company = getCompany("getCompanyFavorGain", companyName);
return company.getFavorGain();
},
checkFactionInvitations: function (): any {
checkFactionInvitations: function (): string[] {
helper.updateDynamicRam("checkFactionInvitations", getRamCost(player, "checkFactionInvitations"));
helper.checkSingularityAccess("checkFactionInvitations");
// Make a copy of player.factionInvitations
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.checkSingularityAccess("joinFaction");
getFaction("joinFaction", name);
getFaction("joinFaction", facName);
if (!player.factionInvitations.includes(name)) {
workerScript.log("joinFaction", () => `You have not been invited by faction '${name}'`);
if (!player.factionInvitations.includes(facName)) {
workerScript.log("joinFaction", () => `You have not been invited by faction '${facName}'`);
return false;
}
const fac = Factions[name];
const fac = Factions[facName];
joinFaction(fac);
// Update Faction Invitation list to account for joined + banned factions
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);
i--;
}
}
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 5);
workerScript.log("joinFaction", () => `Joined the '${name}' faction.`);
workerScript.log("joinFaction", () => `Joined the '${facName}' faction.`);
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.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 (player.inGang() && AllGangs[name] !== undefined) {
workerScript.log("workForFaction", () => `Faction '${name}' does not offer work at the moment.`);
if (player.inGang() && AllGangs[facName] !== undefined) {
workerScript.log("workForFaction", () => `Faction '${facName}' does not offer work at the moment.`);
return false;
}
if (!player.factions.includes(name)) {
workerScript.log("workForFaction", () => `You are not a member of '${name}'`);
if (!player.factions.includes(facName)) {
workerScript.log("workForFaction", () => `You are not a member of '${facName}'`);
return false;
}
@ -1036,7 +1083,7 @@ export function NetscriptSingularity(
workerScript.log("workForFaction", () => txt);
}
const fac = Factions[name];
const fac = Factions[facName];
// Arrays listing factions that allow each time of work
switch (type.toLowerCase()) {
@ -1096,34 +1143,42 @@ export function NetscriptSingularity(
}
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.checkSingularityAccess("getFactionRep");
const faction = getFaction("getFactionRep", name);
const faction = getFaction("getFactionRep", facName);
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.checkSingularityAccess("getFactionFavor");
const faction = getFaction("getFactionFavor", name);
const faction = getFaction("getFactionFavor", facName);
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.checkSingularityAccess("getFactionFavorGain");
const faction = getFaction("getFactionFavorGain", name);
const faction = getFaction("getFactionFavorGain", facName);
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.checkSingularityAccess("donateToFaction");
const faction = getFaction("donateToFaction", name);
const faction = getFaction("donateToFaction", facName);
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;
}
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;
}
if (typeof amt !== "number" || amt <= 0 || isNaN(amt)) {
@ -1133,7 +1188,7 @@ export function NetscriptSingularity(
if (player.money < amt) {
workerScript.log(
"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;
}
@ -1152,13 +1207,15 @@ export function NetscriptSingularity(
workerScript.log(
"donateToFaction",
() =>
`${numeralWrapper.formatMoney(amt)} donated to '${name}' for ${numeralWrapper.formatReputation(
`${numeralWrapper.formatMoney(amt)} donated to '${facName}' for ${numeralWrapper.formatReputation(
repGain,
)} reputation`,
);
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.checkSingularityAccess("createProgram");
@ -1168,12 +1225,10 @@ export function NetscriptSingularity(
workerScript.log("createProgram", () => txt);
}
name = name.toLowerCase();
const p = Object.values(Programs).find((p) => p.name.toLowerCase() === name);
const p = Object.values(Programs).find((p) => p.name.toLowerCase() === programName);
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;
}
@ -1204,10 +1259,11 @@ export function NetscriptSingularity(
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("createProgram", () => `Began creating program: '${name}'`);
workerScript.log("createProgram", () => `Began creating program: '${programName}'`);
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.checkSingularityAccess("commitCrime");
@ -1227,7 +1283,8 @@ export function NetscriptSingularity(
workerScript.log("commitCrime", () => `Attempting to commit ${crime.name}...`);
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.checkSingularityAccess("getCrimeChance");
@ -1238,7 +1295,8 @@ export function NetscriptSingularity(
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.checkSingularityAccess("getCrimeStats");
@ -1260,7 +1318,8 @@ export function NetscriptSingularity(
}
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.checkSingularityAccess("getDarkwebProgramCost");
@ -1272,7 +1331,6 @@ export function NetscriptSingularity(
return -1;
}
programName = programName.toLowerCase();
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

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

@ -22,28 +22,29 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
}
return {
width: function (): number {
helper.updateDynamicRam("width", getRamCost(player, "stanek", "width"));
checkStanekAPIAccess("width");
giftWidth: function (): number {
helper.updateDynamicRam("giftWidth", getRamCost(player, "stanek", "giftWidth"));
checkStanekAPIAccess("giftWidth");
return staneksGift.width();
},
height: function (): number {
helper.updateDynamicRam("height", getRamCost(player, "stanek", "height"));
checkStanekAPIAccess("height");
giftHeight: function (): number {
helper.updateDynamicRam("giftHeight", getRamCost(player, "stanek", "giftHeight"));
checkStanekAPIAccess("giftHeight");
return staneksGift.height();
},
charge: function (_rootX: unknown, _rootY: unknown): Promise<void> {
const rootX = helper.number("stanek.charge", "rootX", _rootX);
const rootY = helper.number("stanek.charge", "rootY", _rootY);
chargeFragment: function (_rootX: unknown, _rootY: unknown): Promise<void> {
const rootX = helper.number("stanek.chargeFragment", "rootX", _rootX);
const rootY = helper.number("stanek.chargeFragment", "rootY", _rootY);
helper.updateDynamicRam("charge", getRamCost(player, "stanek", "charge"));
checkStanekAPIAccess("charge");
helper.updateDynamicRam("chargeFragment", getRamCost(player, "stanek", "chargeFragment"));
checkStanekAPIAccess("chargeFragment");
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;
return netscriptDelay(time, workerScript).then(function () {
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();
});
},
@ -61,49 +62,49 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
return { ...af.copy(), ...af.fragment().copy() };
});
},
clear: function (): void {
helper.updateDynamicRam("clear", getRamCost(player, "stanek", "clear"));
checkStanekAPIAccess("clear");
workerScript.log("stanek.clear", () => `Cleared Stanek's Gift.`);
clearGift: function (): void {
helper.updateDynamicRam("clearGift", getRamCost(player, "stanek", "clearGift"));
checkStanekAPIAccess("clearGift");
workerScript.log("stanek.clearGift", () => `Cleared Stanek's Gift.`);
staneksGift.clear();
},
canPlace: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
const rootX = helper.number("stanek.canPlace", "rootX", _rootX);
const rootY = helper.number("stanek.canPlace", "rootY", _rootY);
const rotation = helper.number("stanek.canPlace", "rotation", _rotation);
const fragmentId = helper.number("stanek.canPlace", "fragmentId", _fragmentId);
helper.updateDynamicRam("canPlace", getRamCost(player, "stanek", "canPlace"));
checkStanekAPIAccess("canPlace");
canPlaceFragment: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
const rootX = helper.number("stanek.canPlaceFragment", "rootX", _rootX);
const rootY = helper.number("stanek.canPlaceFragment", "rootY", _rootY);
const rotation = helper.number("stanek.canPlaceFragment", "rotation", _rotation);
const fragmentId = helper.number("stanek.canPlaceFragment", "fragmentId", _fragmentId);
helper.updateDynamicRam("canPlaceFragment", getRamCost(player, "stanek", "canPlaceFragment"));
checkStanekAPIAccess("canPlaceFragment");
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);
return can;
},
place: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
const rootX = helper.number("stanek.place", "rootX", _rootX);
const rootY = helper.number("stanek.place", "rootY", _rootY);
const rotation = helper.number("stanek.place", "rotation", _rotation);
const fragmentId = helper.number("stanek.place", "fragmentId", _fragmentId);
helper.updateDynamicRam("place", getRamCost(player, "stanek", "place"));
checkStanekAPIAccess("place");
placeFragment: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
const rootX = helper.number("stanek.placeFragment", "rootX", _rootX);
const rootY = helper.number("stanek.placeFragment", "rootY", _rootY);
const rotation = helper.number("stanek.placeFragment", "rotation", _rotation);
const fragmentId = helper.number("stanek.placeFragment", "fragmentId", _fragmentId);
helper.updateDynamicRam("placeFragment", getRamCost(player, "stanek", "placeFragment"));
checkStanekAPIAccess("placeFragment");
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);
},
get: function (_rootX: unknown, _rootY: unknown): IActiveFragment | undefined {
const rootX = helper.number("stanek.get", "rootX", _rootX);
const rootY = helper.number("stanek.get", "rootY", _rootY);
helper.updateDynamicRam("get", getRamCost(player, "stanek", "get"));
checkStanekAPIAccess("get");
getFragment: function (_rootX: unknown, _rootY: unknown): IActiveFragment | undefined {
const rootX = helper.number("stanek.getFragment", "rootX", _rootX);
const rootY = helper.number("stanek.getFragment", "rootY", _rootY);
helper.updateDynamicRam("getFragment", getRamCost(player, "stanek", "getFragment"));
checkStanekAPIAccess("getFragment");
const fragment = staneksGift.findFragment(rootX, rootY);
if (fragment !== undefined) return fragment.copy();
return undefined;
},
remove: function (_rootX: unknown, _rootY: unknown): boolean {
const rootX = helper.number("stanek.remove", "rootX", _rootX);
const rootY = helper.number("stanek.remove", "rootY", _rootY);
helper.updateDynamicRam("remove", getRamCost(player, "stanek", "remove"));
checkStanekAPIAccess("remove");
removeFragment: function (_rootX: unknown, _rootY: unknown): boolean {
const rootX = helper.number("stanek.removeFragment", "rootX", _rootX);
const rootY = helper.number("stanek.removeFragment", "rootY", _rootY);
helper.updateDynamicRam("removeFragment", getRamCost(player, "stanek", "removeFragment"));
checkStanekAPIAccess("removeFragment");
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
* cant be converted to TypeScript.
*/
import { Resleeve } from "./Resleeving/Resleeve";
import { Sleeve } from "./Sleeve/Sleeve";
import { IMap } from "../types";
@ -65,7 +64,6 @@ export interface IPlayer {
playtimeSinceLastBitnode: number;
purchasedServers: any[];
queuedAugmentations: IPlayerOwnedAugmentation[];
resleeves: Resleeve[];
scriptProdSinceLastAug: number;
sleeves: Sleeve[];
sleevesFromCovenant: number;
@ -130,6 +128,8 @@ export interface IPlayer {
factionWorkType: string;
createProgramName: string;
timeWorkedCreateProgram: number;
graftAugmentationName: string;
timeWorkedGraftAugmentation: number;
crimeType: string;
committingCrimeThruSingFn: boolean;
singFnCrimeWorkerScript: WorkerScript | null;
@ -160,6 +160,8 @@ export interface IPlayer {
workChaExpGainRate: number;
workMoneyLossRate: number;
entropy: number;
// Methods
work(numCycles: number): boolean;
workPartTime(numCycles: number): boolean;
@ -181,7 +183,7 @@ export interface IPlayer {
canAccessBladeburner(): boolean;
canAccessCorporation(): boolean;
canAccessGang(): boolean;
canAccessResleeving(): boolean;
canAccessGrafting(): boolean;
canAfford(cost: number): boolean;
gainHackingExp(exp: number): void;
gainStrengthExp(exp: number): void;
@ -286,4 +288,8 @@ export interface IPlayer {
setMult(name: string, mult: number): void;
canAccessCotMG(): boolean;
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 { IMap } from "../../types";
import { Resleeve } from "../Resleeving/Resleeve";
import { Sleeve } from "../Sleeve/Sleeve";
import { IPlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile";
import { Exploit } from "../../Exploits/Exploit";
@ -72,7 +71,6 @@ export class PlayerObject implements IPlayer {
playtimeSinceLastBitnode: number;
purchasedServers: any[];
queuedAugmentations: IPlayerOwnedAugmentation[];
resleeves: Resleeve[];
scriptProdSinceLastAug: number;
sleeves: Sleeve[];
sleevesFromCovenant: number;
@ -139,6 +137,8 @@ export class PlayerObject implements IPlayer {
factionWorkType: string;
createProgramName: string;
timeWorkedCreateProgram: number;
graftAugmentationName: string;
timeWorkedGraftAugmentation: number;
crimeType: string;
committingCrimeThruSingFn: boolean;
singFnCrimeWorkerScript: WorkerScript | null;
@ -169,6 +169,8 @@ export class PlayerObject implements IPlayer {
workChaExpGainRate: number;
workMoneyLossRate: number;
entropy: number;
// Methods
work: (numCycles: number) => boolean;
workPartTime: (numCycles: number) => boolean;
@ -190,7 +192,7 @@ export class PlayerObject implements IPlayer {
canAccessBladeburner: () => boolean;
canAccessCorporation: () => boolean;
canAccessGang: () => boolean;
canAccessResleeving: () => boolean;
canAccessGrafting: () => boolean;
canAfford: (cost: number) => boolean;
gainHackingExp: (exp: number) => void;
gainStrengthExp: (exp: number) => void;
@ -296,6 +298,10 @@ export class PlayerObject implements IPlayer {
setMult: (name: string, mult: number) => void;
canAccessCotMG: () => boolean;
sourceFileLvl: (n: number) => number;
startGraftAugmentationWork: (augmentationName: string, time: number) => void;
graftAugmentationWork: (numCycles: number) => boolean;
finishGraftAugmentationWork: (cancelled: boolean) => string;
applyEntropy: (stacks?: number) => void;
constructor() {
//Skills and stats
@ -419,6 +425,9 @@ export class PlayerObject implements IPlayer {
this.createProgramName = "";
this.createProgramReqLvl = 0;
this.graftAugmentationName = "";
this.timeWorkedGraftAugmentation = 0;
this.className = "";
this.crimeType = "";
@ -457,11 +466,12 @@ export class PlayerObject implements IPlayer {
// Sleeves & Re-sleeving
this.sleeves = [];
this.resleeves = [];
this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenan;
//bitnode
this.bitNodeN = 1;
this.entropy = 0;
//Used to store the last update time.
this.lastUpdate = 0;
this.lastSave = 0;
@ -541,6 +551,9 @@ export class PlayerObject implements IPlayer {
this.startCreateProgramWork = generalMethods.startCreateProgramWork;
this.createProgramWork = generalMethods.createProgramWork;
this.finishCreateProgramWork = generalMethods.finishCreateProgramWork;
this.startGraftAugmentationWork = generalMethods.startGraftAugmentationWork;
this.graftAugmentationWork = generalMethods.craftAugmentationWork;
this.finishGraftAugmentationWork = generalMethods.finishGraftAugmentationWork;
this.startClass = generalMethods.startClass;
this.takeClass = generalMethods.takeClass;
this.finishClass = generalMethods.finishClass;
@ -577,7 +590,7 @@ export class PlayerObject implements IPlayer {
this.gainCodingContractReward = generalMethods.gainCodingContractReward;
this.travel = generalMethods.travel;
this.gotoLocation = generalMethods.gotoLocation;
this.canAccessResleeving = generalMethods.canAccessResleeving;
this.canAccessGrafting = generalMethods.canAccessGrafting;
this.giveExploit = generalMethods.giveExploit;
this.giveAchievement = generalMethods.giveAchievement;
this.getIntelligenceBonus = generalMethods.getIntelligenceBonus;
@ -611,6 +624,8 @@ export class PlayerObject implements IPlayer {
this.canAccessCotMG = generalMethods.canAccessCotMG;
this.sourceFileLvl = generalMethods.sourceFileLvl;
this.applyEntropy = augmentationMethods.applyEntropy;
}
/**

@ -5,6 +5,8 @@ import { IPlayer } from "../IPlayer";
import { Augmentation } from "../../Augmentation/Augmentation";
import { calculateEntropy } from "../Grafting/EntropyAccumulation";
export function hasAugmentation(this: IPlayer, aug: string | Augmentation, installed = false): boolean {
const augName: string = aug instanceof Augmentation ? aug.name : aug;
@ -24,3 +26,14 @@ export function hasAugmentation(this: IPlayer, aug: string | Augmentation, insta
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.resleeves = [];
const numSleeves = Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant;
if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves;
for (let i = this.sleeves.length; i < numSleeves; i++) {
@ -182,6 +180,7 @@ export function prestigeAugmentation(this: PlayerObject): void {
}
export function prestigeSourceFile(this: IPlayer): void {
this.entropy = 0;
this.prestigeAugmentation();
this.karma = 0;
// 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.timeWorkedCreateProgram = 0;
this.timeWorkedGraftAugmentation = 0;
this.currentWorkFactionName = "";
this.currentWorkFactionDescription = "";
this.createProgramName = "";
this.graftAugmentationName = "";
this.className = "";
this.workType = "";
}
@ -609,6 +610,10 @@ export function process(this: IPlayer, router: IRouter, numCycles = 1): void {
if (this.workPartTime(numCycles)) {
router.toCity();
}
} else if (this.workType === CONSTANTS.WorkTypeGraftAugmentation) {
if (this.graftAugmentationWork(numCycles)) {
router.toGrafting();
}
} else if (this.work(numCycles)) {
router.toCity();
}
@ -1329,6 +1334,59 @@ export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): stri
this.resetWorkStatus();
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 */
export function startClass(this: IPlayer, costMult: number, expMult: number, className: string): void {
this.resetWorkStatus();
@ -2640,7 +2698,7 @@ export function gotoLocation(this: IPlayer, to: LocationName): boolean {
return true;
}
export function canAccessResleeving(this: IPlayer): boolean {
export function canAccessGrafting(this: IPlayer): boolean {
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
initMessages();
// Apply entropy from grafting
Player.applyEntropy(Player.entropy);
// Gang
const gang = Player.gang;
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) {
func = workerScript.env.vars.ui[ref];
refDetail = `ui.${ref}`;
} else if (ref in workerScript.env.vars.grafting) {
func = workerScript.env.vars.grafting[ref];
refDetail = `grafting.${ref}`;
} else {
func = workerScript.env.vars[ref];
refDetail = `${ref}`;

@ -95,6 +95,7 @@ interface Player {
tor: boolean;
hasCorporation: boolean;
inBladeburner: boolean;
entropy: number;
}
/**
@ -666,6 +667,10 @@ export interface CharacterMult {
agility: number;
/** Agility exp */
agilityExp: number;
/** Charisma stat */
charisma: number;
/** Charisma exp */
charismaExp: number;
/** Company reputation */
companyRep: number;
/** Money earned from crimes */
@ -706,10 +711,10 @@ export interface CharacterInfo {
factions: string[];
/** Current health points */
hp: number;
/** Array of all companies at which you have jobs */
company: string[];
/** Array of all jobs */
jobs: string[];
/** Array of job positions for all companies you are employed at. Same order as 'jobs' */
jobTitle: string[];
jobTitles: string[];
/** Maximum health points */
maxHp: number;
/** Boolean indicating whether or not you have a tor router */
@ -734,6 +739,18 @@ export interface CharacterInfo {
workRepGain: number;
/** Money earned so far from work, if applicable */
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
* 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;
@ -2198,11 +2215,8 @@ export interface Singularity {
* Hospitalize the player.
* @remarks
* RAM cost: 0.25 GB * 16/4/1
*
*
* @returns The cost of the hospitalization.
*/
hospitalize(): number;
hospitalize(): void;
/**
* Soft reset the game.
@ -3719,6 +3733,49 @@ export interface Sleeve {
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
* @public
@ -4028,14 +4085,14 @@ interface Stanek {
* RAM cost: 0.4 GB
* @returns The width of the gift.
*/
width(): number;
giftWidth(): number;
/**
* Stanek's Gift height.
* @remarks
* RAM cost: 0.4 GB
* @returns The height of the gift.
*/
height(): number;
giftHeight(): number;
/**
* 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.
* @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.
@ -4070,7 +4127,7 @@ interface Stanek {
* @remarks
* RAM cost: 0 GB
*/
clear(): void;
clearGift(): void;
/**
* Check if fragment can be placed at specified location.
@ -4083,7 +4140,7 @@ interface Stanek {
* @param fragmentId - fragmentId ID of the fragment to place.
* @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.
* @remarks
@ -4095,7 +4152,7 @@ interface Stanek {
* @param fragmentId - ID of the fragment to place.
* @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.
* @remarks
@ -4105,7 +4162,7 @@ interface Stanek {
* @param rootY - Y against which to align the top left of the fragment.
* @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.
@ -4116,7 +4173,7 @@ interface Stanek {
* @param rootY - Y against which to align the top left of the fragment.
* @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;
/**
* 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.
*

@ -118,6 +118,11 @@ interface IDefaultSettings {
*/
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
*/
@ -197,6 +202,7 @@ export const defaultSettings: IDefaultSettings = {
SuppressBladeburnerPopup: false,
SuppressTIXPopup: false,
SuppressSavedGameToast: false,
SuppressAutosaveDisabledWarnings: false,
UseIEC60027_2: false,
ExcludeRunningScriptsFromSave: false,
IsSidebarOpened: true,
@ -235,6 +241,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup,
SuppressTIXPopup: defaultSettings.SuppressTIXPopup,
SuppressSavedGameToast: defaultSettings.SuppressSavedGameToast,
SuppressAutosaveDisabledWarnings: defaultSettings.SuppressAutosaveDisabledWarnings,
UseIEC60027_2: defaultSettings.UseIEC60027_2,
ExcludeRunningScriptsFromSave: defaultSettings.ExcludeRunningScriptsFromSave,
IsSidebarOpened: defaultSettings.IsSidebarOpened,

@ -617,7 +617,7 @@ export function SidebarRoot(props: IProps): React.ReactElement {
key={"City"}
className={clsx({
[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}
>

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

@ -36,7 +36,7 @@ export function connect(
terminal.connectToServer(player, hostname);
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 {
terminal.error("Host not found");
}

@ -15,6 +15,7 @@ import { Factions, initFactions } from "./Faction/Factions";
import { staneksGift } from "./CotMG/Helper";
import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers";
import { Router } from "./ui/GameRoot";
import { Page } from "./ui/Router";
import { SetupTextEditor } from "./ScriptEditor/ui/ScriptEditorRoot";
import {
@ -48,7 +49,8 @@ import { calculateAchievements } from "./Achievements/Achievements";
import React from "react";
import { setupUncaughtPromiseHandler } from "./UncaughtPromiseHandler";
import { Typography } from "@mui/material";
import { Button, Typography } from "@mui/material";
import { SnackbarEvents } from "./ui/React/Snackbar";
const Engine: {
_lastUpdate: number;
@ -187,7 +189,8 @@ const Engine: {
Settings.AutosaveInterval = 60;
}
if (Settings.AutosaveInterval === 0) {
Engine.Counters.autoSaveCounter = Infinity;
warnAutosaveDisabled();
Engine.Counters.autoSaveCounter = 60 * 5; // Let's check back in a bit
} else {
Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5;
saveObject.saveGame(!Settings.SuppressSavedGameToast);
@ -260,6 +263,9 @@ const Engine: {
initSymbolToStockMap();
}
// Apply penalty for entropy accumulation
Player.applyEntropy(Player.entropy);
// Calculate the number of cycles have elapsed while offline
Engine._lastUpdate = new Date().getTime();
const lastUpdate = Player.lastUpdate;
@ -299,6 +305,8 @@ const Engine: {
Player.commitCrime(numCyclesOffline);
} else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
Player.workPartTime(numCyclesOffline);
} else if (Player.workType === CONSTANTS.WorkTypeGraftAugmentation) {
Player.graftAugmentationWork(numCyclesOffline);
} else {
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 };

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

@ -143,7 +143,9 @@ function Work(): React.ReactElement {
let details = <></>;
let header = <></>;
let innerText = <></>;
if (player.workType === CONSTANTS.WorkTypeCompanyPartTime || player.workType === CONSTANTS.WorkTypeCompany) {
switch (player.workType) {
case CONSTANTS.WorkTypeCompanyPartTime:
case CONSTANTS.WorkTypeCompany:
details = (
<>
{player.jobs[player.companyName]} at <strong>{player.companyName}</strong>
@ -159,7 +161,8 @@ function Work(): React.ReactElement {
+<Reputation reputation={player.workRepGained} /> rep
</>
);
} else if (player.workType === CONSTANTS.WorkTypeFaction) {
break;
case CONSTANTS.WorkTypeFaction:
details = (
<>
{player.factionWorkType} for <strong>{player.currentWorkFactionName}</strong>
@ -175,11 +178,13 @@ function Work(): React.ReactElement {
+<Reputation reputation={player.workRepGained} /> rep
</>
);
} else if (player.workType === CONSTANTS.WorkTypeStudyClass) {
break;
case CONSTANTS.WorkTypeStudyClass:
details = <>{player.workType}</>;
header = <>You are {player.className}</>;
innerText = <>{convertTimeMsToTimeElapsedString(player.timeWorked)}</>;
} else if (player.workType === CONSTANTS.WorkTypeCreateProgram) {
break;
case CONSTANTS.WorkTypeCreateProgram:
details = <>Coding {player.createProgramName}</>;
header = <>Creating a program</>;
innerText = (
@ -188,6 +193,16 @@ function Work(): React.ReactElement {
{((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 (
@ -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", flex: 1, justifyContent: "flex-start", alignItems: "center" }}>
<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"} />
</Tooltip>
</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.</>}
/>
</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>
<OptionSwitch
checked={Settings.DisableHotkeys}

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

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

@ -41,8 +41,8 @@ export function WorkInProgressRoot(): React.ReactElement {
return (
<>
<Typography variant="h4" color="primary">
You have not joined {player.currentWorkFactionName || "(Faction not found)"} yet or cannot work at this time,
please try again if you think this should have worked
You have not joined {player.currentWorkFactionName || "(Faction not found)"} yet or cannot work at this
time, please try again if you think this should have worked
</Typography>
<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();
return <></>;

@ -16,7 +16,6 @@ import "numeral/locales/ru";
import { Settings } from "../Settings/Settings";
const extraFormats = [1e15, 1e18, 1e21, 1e24, 1e27, 1e30];
const extraNotations = ["q", "Q", "s", "S", "o", "n"];
const gigaMultiplier = { standard: 1e9, iec60027_2: 2 ** 30 };
@ -52,7 +51,7 @@ class NumeralFormatter {
}
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
@ -67,10 +66,10 @@ class NumeralFormatter {
}
}
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");
if (str === "NaNt") return this.format(n, "0.[" + " ".repeat(decimalPlaces) + "]e+0");
const str = this.format(n, "0." + "0".repeat(decimalPlaces) + "a");
if (str === "NaNt") return this.format(n, "0." + " ".repeat(decimalPlaces) + "e+0");
return str;
}
@ -113,8 +112,7 @@ class NumeralFormatter {
}
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.standard, "0.00b");
@ -189,7 +187,7 @@ class NumeralFormatter {
}
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);
if (match == null) {
return NaN;
@ -208,9 +206,12 @@ class NumeralFormatter {
if (isNaN(n3)) n3 = 0;
const largestAbsolute = Math.max(Math.abs(n1), Math.abs(n2), Math.abs(n3));
switch (largestAbsolute) {
case Math.abs(n1): return n1;
case Math.abs(n2): return n2;
case Math.abs(n3): return n3;
case Math.abs(n1):
return n1;
case Math.abs(n2):
return n2;
case Math.abs(n3):
return n3;
}
return 0;
}
@ -222,21 +223,29 @@ class NumeralFormatter {
const parsed = parseFloat(s);
const selfParsed = this.parseCustomLargeNumber(s);
// 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;
} else if (isNaN(parsed) && isNaN(selfParsed)) { // 2x NaN
} else if (isNaN(parsed) && isNaN(selfParsed)) {
// 2x NaN
return numeralValue;
} else if (numeralValue === null && isNaN(selfParsed)) { // 2x NaN
} else if (numeralValue === null && isNaN(selfParsed)) {
// 2x NaN
return parsed;
} else if (isNaN(parsed) && numeralValue === null) { // 2x NaN
} else if (isNaN(parsed) && numeralValue === null) {
// 2x NaN
return selfParsed;
} else if (isNaN(parsed)) { // 1x NaN
} else if (isNaN(parsed)) {
// 1x NaN
return this.largestAbsoluteNumber(numeralValue, selfParsed);
} else if (numeralValue === null) { // 1x NaN
} else if (numeralValue === null) {
// 1x NaN
return this.largestAbsoluteNumber(parsed, selfParsed);
} else if (isNaN(selfParsed)) { // 1x NaN
} else if (isNaN(selfParsed)) {
// 1x NaN
return this.largestAbsoluteNumber(numeralValue, parsed);
} else { // no NaN
} else {
// no NaN
return this.largestAbsoluteNumber(numeralValue, parsed, selfParsed);
}
}