Compare commits

..

23 Commits

Author SHA1 Message Date
catloversg b2a9aaf350 API: Change getDarkwebPrograms return type to ProgramName[] (#2754) 2026-05-11 19:20:08 -07:00
catloversg 9ea1ec0f28 MISC: Support angle bracket type assertions in RAM calculation (#2751) 2026-05-09 23:59:39 -07:00
catloversg b79d5b1017 DARKNET: Prevent generating malformed darknet server hostname (#2744) 2026-05-09 16:18:19 -07:00
Berdes 0fe28a9fea DOC: remove spoiler for Offline scripts and bonus time page and make it accessible early-game (#2749)
* DOC: remove spoiler for Offline scripts and bonus time page and make it accessible early-game

It was suggested in
https://discord.com/channels/415207508303544321/415213435974975508/1501708264147259472

I manually checked that all the modified links work in-game.

* Move the link next to another non-spoiler link
2026-05-09 16:12:33 -07:00
Berdes 8c9635a7a8 DOC: Remove non-existent influence namespace in Darknet documentation (#2748)
Mentioned in
https://discord.com/channels/415207508303544321/415213413745164318/1501709972705378336
2026-05-09 16:08:58 -07:00
Michael Ficocelli a7f54ea152 DNET: Fix money mults on hard nodes (#2743) 2026-05-07 16:18:49 -07:00
gmcew b9699c2a95 UI: Break out Darknet BN and player mults as separate entries (#2745)
On the stats page, this change separates Darknet mults from crime in player mults, and additionally includes the BN mults for Darknet.
2026-05-07 16:14:31 -07:00
Mads Ahlquist Jensen 7dd06b5b37 Add HJKL key mappings for infiltration arrows (#2742) 2026-05-07 15:36:27 -07:00
catloversg eb431145ee UI: Show hints of Gang mechanic in pre-endgame (#2723) 2026-05-06 15:22:43 -07:00
Andrey Andreyevich Bienkowski 2ab144cff2 CLI: Add new command to upload a directory (#2659) 2026-05-06 15:20:42 -07:00
catloversg 7c6d147ff7 BUGFIX: Player can switch tabs without losing focus on current work (#2724) 2026-05-06 15:11:30 -07:00
catloversg 4ee26edb85 MISC: Update game version (#2732) 2026-05-06 15:07:36 -07:00
catloversg a99197a9ea BUGFIX: Tutorial links to outdated faq url (#2733) 2026-05-06 15:06:58 -07:00
catloversg c616ba3b46 DARKNET: Fix typo in authentication response message (#2734) 2026-05-06 15:05:50 -07:00
catloversg a7b34b110c DOCUMENTATION: Fix incorrect cloud API example (#2738) 2026-05-06 14:30:48 -07:00
Michael Ficocelli 15a67d0156 DNET: Cache reward fixes (#2731) 2026-05-05 15:56:02 -07:00
CTN 530392eeee DOC: Update list of RFA community tools (#2722) 2026-05-05 15:53:25 -07:00
catloversg 2ef68d31ae MISC: Cancel faction work instead of finishing it when creating gang (#2726) 2026-05-04 15:24:08 -07:00
catloversg ec307241e0 CODEBASE: Remove duplicate getStockFromSymbol function (#2725) 2026-05-04 14:31:20 -07:00
Michael Ficocelli d324a93bac DNET: Remove TS type annotation from doc example script (#2721) 2026-05-04 14:29:52 -07:00
Snarling d3bdfc44a4 WORKFLOWS: Update action versions
Missed one
2026-05-02 02:05:21 -04:00
Snarling 0fb913d158 WORKFLOWS: Update action versions (#2718) 2026-05-02 01:58:38 -04:00
Snarling 63c3993995 Initial changes for 3.0.1 dev cycle (#2716) 2026-05-01 23:15:47 -04:00
57 changed files with 701 additions and 742 deletions
+11 -11
View File
@@ -22,11 +22,11 @@ jobs:
name: Build Windows
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
ref: ${{ github.event.inputs.git-sha || inputs.git-sha || github.sha }}
- name: Use Node.js 24
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 24
cache: "npm"
@@ -39,14 +39,14 @@ jobs:
shell: bash
run: npm run electron-win
- name: Upload x64 artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: bitburner-win32-x64
include-hidden-files: true
path: .build/bitburner-win32-x64/*
if-no-files-found: error
- name: Upload arm64 artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: bitburner-win32-arm64
include-hidden-files: true
@@ -57,11 +57,11 @@ jobs:
name: Build Linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
ref: ${{ github.event.inputs.git-sha || inputs.git-sha || github.sha }}
- name: Use Node.js 24
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 24
cache: "npm"
@@ -72,14 +72,14 @@ jobs:
- name: Build the Electron app
run: npm run electron-linux
- name: Upload x64 artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: bitburner-linux-x64
include-hidden-files: true
path: .build/bitburner-linux-x64/*
if-no-files-found: error
- name: Upload arm64 artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: bitburner-linux-arm64
include-hidden-files: true
@@ -90,11 +90,11 @@ jobs:
name: Build macOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
ref: ${{ github.event.inputs.git-sha || inputs.git-sha || github.sha }}
- name: Use Node.js 24
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 24
cache: "npm"
@@ -105,7 +105,7 @@ jobs:
- name: Build the Electron app
run: npm run electron-mac
- name: Upload darwin-universal artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: bitburner-darwin-universal
include-hidden-files: true
+2 -2
View File
@@ -19,9 +19,9 @@ jobs:
contents: write
needs: [build]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Download artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
path: .build
pattern: bitburner-*
+4 -4
View File
@@ -25,8 +25,8 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- run: npm ci
@@ -38,9 +38,9 @@ jobs:
else
npm run build
fi
- uses: actions/upload-pages-artifact@v3
- uses: actions/upload-pages-artifact@v5
with:
path: ".app"
- name: Deploy to gh pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "bitburner",
"version": "3.0.0",
"version": "3.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "3.0.0",
"version": "3.0.1",
"dependencies": {
"@catloversg/steamworks.js": "0.0.3",
"arg": "^5.0.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "bitburner",
"version": "3.0.0",
"version": "3.0.1",
"description": "A cyberpunk-themed programming incremental game",
"main": "main.js",
"author": "Daniel Xie, hydroflame, et al.",
@@ -9,11 +9,11 @@ Get a list of programs offered on the dark web.
**Signature:**
```typescript
getDarkwebPrograms(): string[];
getDarkwebPrograms(): ProgramName[];
```
**Returns:**
string\[\]
[ProgramName](./bitburner.programname.md)<!-- -->\[\]
- a list of programs available for purchase on the dark web, or \[\] if Tor has not been purchased
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "bitburner",
"version": "3.0.0",
"version": "3.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "3.0.0",
"version": "3.0.1",
"hasInstallScript": true,
"license": "SEE LICENSE IN license.txt",
"dependencies": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "bitburner",
"license": "SEE LICENSE IN license.txt",
"version": "3.0.0",
"version": "3.0.1",
"main": "electron-main.js",
"author": {
"name": "Daniel Xie, hydroflame, et al."
+2 -2
View File
@@ -836,7 +836,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier
StaneksGiftPowerMultiplier: 0.5,
StaneksGiftExtraSize: 2,
DarknetMoneyMultiplier: 0.5,
DarknetMoneyMultiplier: 0.05,
WorldDaemonDifficulty: 2,
});
@@ -1036,7 +1036,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier
StaneksGiftPowerMultiplier: 2,
StaneksGiftExtraSize: 1,
DarknetMoneyMultiplier: 0.5,
DarknetMoneyMultiplier: 0.1,
WorldDaemonDifficulty: 3,
});
@@ -68,6 +68,7 @@ export const BitNodeMultipliersDisplay = ({ n, level, hideMultsIfCannotAccessFea
<CloudServersMults n={n} mults={mults} />
<StockMults n={n} mults={mults} />
<CrimeMults n={n} mults={mults} />
<DarknetMults n={n} mults={mults} />
<InfiltrationMults n={n} mults={mults} />
<CompanyMults n={n} mults={mults} />
<GangMults n={n} mults={mults} hideMultsIfCannotAccessFeature={hideMultsIfCannotAccessFeature} />
@@ -226,6 +227,17 @@ function CrimeMults({ mults }: IMultsProps): React.ReactElement {
return <BNMultTable sectionName="Crime" rowData={rows} mults={mults} />;
}
function DarknetMults({ mults }: IMultsProps): React.ReactElement {
const rows: IBNMultRows = {
DarknetMoneyMultiplier: {
name: "Darknet Money",
color: Settings.theme.money,
},
};
return <BNMultTable sectionName="Darknet" rowData={rows} mults={mults} />;
}
function SkillMults({ mults }: IMultsProps): React.ReactElement {
const rows: IBNMultRows = {
HackingLevelMultiplier: {
+4 -460
View File
@@ -4,8 +4,8 @@
* Constants for specific mechanics or features will NOT be here.
*/
export const CONSTANTS = {
VersionString: "3.0.0",
isDevBranch: false,
VersionString: "3.0.1Dev",
isDevBranch: true,
isInTestEnvironment: globalThis.process?.env?.JEST_WORKER_ID !== undefined,
VersionNumber: 50,
@@ -111,467 +111,11 @@ export const CONSTANTS = {
// Also update Documentation/doc/en/changelog.md when appropriate (when doing a release)
LatestUpdate: `
## v3.0.0 Release: 1 May 2026
### BREAKING CHANGES
- Remove API server (#2084) (@catloversg)
- Remove support for running NS1 scripts (#2083) (@catloversg)
- Enforce stricter param check on ns.getBitNodeMultipliers and ns.hacknet.spendHashes (#2085) (@catloversg)
- Remove APIs that were deprecated a long time ago (#2088) (@catloversg)
- Moved formatting functions to their own interface (#1635) (@G4mingJon4s)
- Remove fuzzy matching when checking params (#2091) (@catloversg)
- Remove deprecated tail-related APIs (#2143) (@catloversg)
- Rename setAutoJobAssignment API to setJobAssignment (#2146) (@catloversg)
- Make nuke and port cracking APIs return false instead of throwing error (#1023, #2153) (@Hydrogeniouss, @catloversg)
- Standardize names of Stock APIs (#2173) (@catloversg)
- Rename BN multiplier RepToDonateToFaction to FavorToDonateToFaction (#2178) (@catloversg)
- Make ns.bladeburner.getActionRepGain return the expected reputation gain (#2186) (@catloversg)
- Rename FactionName.BachmanAssociates to FactionName.BachmanAndAssociates (#2048, #2183) (@masarakki, @catloversg)
- Remove DreamSense upgrade (#2232) (@catloversg)
- Use different term for dividend modifier instead of tax (#2237) (@catloversg)
- Remove Spring Water industry (#2240) (@catloversg)
- Remove VeChain (#2245) (@catloversg)
- Make some APIs throw error when server is invalid (#2261) (@catloversg)
- Rename equipment that uses real brand names (#2293) (@catloversg)
- Make TIX access independent from WSE account (#2342) (@catloversg)
- Move and rename purchased server functions to cloud API (#2367) (@gmcew)
- Fix: Coding contracts may have duplicate names (#2399) (@aaaa-imcute)
- Generate test contracts on executing host by default. Add support for optional parameter to specify the server (#2417) (@1337JiveTurkey)
- The "darkweb" server becomes a darknet server (Check new Dark Net feature in #2139) (@ficocelliguy)
- Remove RAM cost of hacknet namespace and set RAM cost of each hacknet API (#2502) (@catloversg)
- Cancel sleeve's current task when calling ns.sleeve.travel() (#2559) (@catloversg)
- Make ns.cloud.purchaseServer() and ns.cloud.deleteServer() use hostname as provided (#2560) (@catloversg)
- Make implicit string conversion consistent across all coding contracts (#2608) (@catloversg)
- Rename ns.gang.getOtherGangInformation to getAllGangInformation (#2635) (@lstutzman)
- Standardize "nextCompletion" promise in tasks (#2687) (@catloversg)
## v3.0.1 Dev: 1 May 2026
### MAJOR CHANGES
- Added Darknet, a new mechanic based on spreading through an unstable network and cracking passwords. Purchase DarkscapeNavigator.exe in the terminal to unlock access. (#2139) (@ficocelliguy)
- Balance change: IPvGO: Improve favor gain from wins to balance around the rep value of favor (#2131) (@ficocelliguy)
- Search and read NS API docs in editor tab and documentation tab (#2163) (@catloversg)
- Balance change: Infiltration: Rebalance rewards, add min stat requirement, add market demand (#2210) (@ficocelliguy, @d0sboots, @catloversg)
- No changes yet, initial dev branch.
### UI
- Fix: Hacknet's RAM upgrade button is off-by-one (#2093) (@catloversg)
- Add visual indicators for tooltips of reputation/favor (#2092) (@catloversg)
- Mitigate crash in Terminal page in edge cases (#2099) (@catloversg)
- Update favicon files (#2122) (@catloversg)
- Add option to enable/disable syncing Steam achievements (#2117) (@catloversg)
- Change min and step value of "Tail render interval" setting (#2129) (@catloversg)
- Realign and update text in achievement icons (#2127) (@catloversg)
- Show achievement lists in grids (#2109) (@catloversg)
- Fix styling of IPvGO score modal (#2166) (@ficocelliguy)
- Add RAM usage and file size to "ls -l" output (#2135) (@HansLuft778)
- Fix: Coding contract UI does not handle error properly when answer format is invalid (#2171) (@catloversg)
- Improve Recovery Mode screen (#2206) (@catloversg)
- Add "Recent Errors" tab and improved error modal (#2169) (@ficocelliguy)
- Validate bet input of casino mini games (#1694) (@catloversg)
- Scroll to top when opening new NS API doc page in popup mode (#2181) (@catloversg)
- Fix nbsp rendering as text rather than a space (#2225) (@gmcew)
- Show terminal warning instead of popup for breaking changes (#2244) (@catloversg)
- Show money in exponential form instead of "0.000" for dividends when it's > 0 but still too small (#2243) (@catloversg)
- Place tooltips and popups in front of log windows (#2253) (@catloversg)
- Fix: Go history page shows favor bonus instead of reputation bonus (#2251) (@catloversg)
- Fix: Error message shows blob URL instead of script name (#2265) (@catloversg)
- Add tooltip for reputation/favor in page of faction's augmentation list (#2268) (@catloversg)
- Fix: Dropdown list appears behind modal when it's used in modal (#2282) (@catloversg)
- Close coding contract popup on prestige (#2285) (@catloversg)
- Add "Run Beautify on Save" option for built-in editor (#2287) (@TheCleric)
- Add configurable option for auto-reconnecting to RFA client (#2297) (@catloversg)
- Fix: Active Scripts page may mix up UI after prestige (#2303) (@catloversg)
- Better status bar animations (#2317) (@d0sboots)
- Prevent text from overflowing when script's args are too long in Active Scripts page (#2324) (@catloversg)
- Print error detail to terminal if script cannot be compiled or there is runtime error while autocompleting (#2328) (@catloversg)
- Fix: Color picker appears behind theme editor (#2321) (@catloversg)
- Allow automatically hiding the top menu in Electron (#2325) (@femboyfireball)
- Clarify why you can't buy 4S when disabled (#2311) (@femboyfireball)
- Warn player if they run no-arg programs with arguments (#2338) (@catloversg)
- Show server's money in exponential form if it's too small (#2343) (@catloversg)
- Format the shared RAM multipler on the factions (#2355) (@mctylr-gh)
- Fix: Electron app does not show achievement icons (#2362) (@catloversg)
- Reword tutorial steps to distinguish between instruction and example (#2370) (@catloversg)
- Prevent showing redundant error toasts on failed RFA auto connect attempts (#2381) (@shyguy1412)
- Fix: Company's job list shows wrong starting jobs (#2391) (@catloversg)
- Improve instructions in recovery mode (#2394) (@catloversg)
- Add option to set fractional digits (#2419) (@CTNOriginals)
- Add links to documentation pages in tutorial's last step (#2424) (@catloversg)
- Show useful error messages when loading unsupported save data from newer versions (#2425) (@catloversg)
- Change notice for breaking changes in v2 (#2426) (@catloversg)
- Do not show error popup related to Stanek's Gift data when loading save files from pre-v1.1.0 (#2429) (@catloversg)
- Add Script Editor toggle for Sticky Scroll (#2431) (@gmcew)
- Display contract answers on completely failed contracts (#2440) (@TheCleric)
- Remove BitNode's difficulty in BitNode selection popup (#2454) (@catloversg)
- Add settings to change currency symbol and whether to show it after money value (#2453) (@kaoticengineering)
- Fix: Monaco shows wrong TSDoc of APIs that have "export" word (#2473) (@catloversg)
- Show karma in Stats page and explain what it is in documentation (#2475) (@catloversg)
- Update icon of DARKNET_BACKDOOR and DARKNET_DEPTHS achievements (#2480) (@catloversg)
- Update incorrect ps command shown in help (#2484) (@Groot-0909)
- Improve help text of scp and run (#2493) (@catloversg)
- Add option to disable minimap in script editor (#2504) (@catloversg)
- Improve navigation system of in-game documentation viewer (#2499) (@catloversg)
- Use font family setting when rendering MUI Link component (#2511) (@catloversg)
- Show hints of BitNode documentation and allow opening it in BitVerse (#2513) (@catloversg)
- Show errors if using nano/vim with patterns that do not match any files (#2515) (@catloversg)
- Tweak CSS/Position of Darknet Docs link (#2517) (@d0sboots)
- Add indicator of RFA connection status to overview panel (#2497) (@catloversg)
- Fix issues with RFA auto-reconnecting feature (#2535) (@catloversg)
- Add inline script RAM usage text to each active script (#2546) (@vadien)
- Update toolbar of in-game editor (#2551) (@catloversg)
- Show "undefined" instead of -1 as pid in error popup when catching promise errors (#2555) (@catloversg)
- Add option to autosave scripts on focus change (#2565) (@catloversg)
- Prevent joining banned factions via UI (#2573) (@catloversg)
- Fix: Import save comparison popup shows wrong BN level (#2595) (@catloversg)
- Fix: Cannot type in text boxes rendered by players' scripts when terminal tab is shown (#2615, #2622) (@lstutzman, @catloversg)
- Navigate to gym/university instead of city when stopping focusing on gym/class work (#2613) (@lstutzman)
- Ensure prompts shown by ns.prompt do not lose focus in the terminal tab (#2631) (@catloversg)
- Remove unnecessary max-width of tab list in in-game editor (#2643) (@catloversg)
- Add hooks to sidebar for players to attach custom content (#2651) (@catloversg)
- Use exponential notation when formatting very small HP or thread values (#2656) (@catloversg)
- Show effective amount of shared RAM when using UI (#2691) (@catloversg)
- Use "success" theme color for low infiltration difficulty instead of "primary" (#2693) (@catloversg)
- Reload immediately after importing, deleting save data or killing all scripts (#2697) (@catloversg)
- Activate recovery mode on critical BN prestige bugs (#2699) (@catloversg)
### MISC
- Ensure IPvGO board has at least 1 offline node (#2072) (@ficocelliguy)
- Fix: Game crashes when generating CCT in weird case (#2077) (@catloversg)
- Add ns.dynamicImport() to dynamically import a script (#2036) (@shyguy1412)
- Add functionality and support to fully allow Players to use IP addresses in place of hostnames (#1990) (@NagaOuroboros)
- Fix: IPvGO tutorial was getting stuck if you left the tab and returned (#2071) (@ficocelliguy)
- Fix: Passive faction reputation gain applies Player.mults.faction_rep twice (#2125) (@catloversg)
- Fix: Electron app does not run on Linux due to incompatible glibc version and wrong usage of net.fetch (#2114) (@catloversg)
- Change how enums are exposed in NetscriptDefinitions.d.ts (#1998) (@catloversg)
- Add formulas API for calculating share power and move UI of sharing RAM (#2126) (@catloversg)
- Use FactionName enum in relevant APIs (#2101) (@catloversg)
- Prevent running multiple instances of Electron app (#2095) (@catloversg)
- Tweak "The Covenant" faction's rumor condition (#2110) (@catloversg)
- Export crash report when a fatal error occurs (#2106) (@catloversg)
- Correctly end game & winstreak if a cheat attempt critically fails (#2130) (@ficocelliguy)
- Show custom error message when player imports decompressed save file (#2108) (@catloversg)
- Add "Total Number of Primes" contract (#2116) (@gmcew)
- Make IP addresses use the full 32 bit space (#2113) (@whiskeyfur)
- Add "--tail" to default autocomplete options (#2103) (@catloversg)
- Update blood donation (#2151, #2216, #2508) (@catloversg, @hydroflame, @d0sboots)
- Fix typo in sector-12-crime.lit (#2159) (@Boingostarr)
- Add versionNumber to ns.ui.getGameInfo() (#2155) (@catloversg)
- Fix typos in literature files (#2164) (@catloversg)
- Add more enums to ns.enums (#2165) (@catloversg)
- Add removal of fuzzy matching to list of breaking changes (#2149) (@catloversg)
- Fix: API break detector does not detect affected code in some cases (#2172) (@catloversg)
- Mention bug-report channel on Discord for reporting bugs (#2201) (@catloversg)
- Add file metadata (timestamps) (#1271, #2199) (@Hoekstraa, @catloversg)
- Allow using E notation in expr CLI (#2209) (@catloversg)
- Fix: Current work is not shown in edge cases (#2208) (@catloversg)
- Make ns.codingcontract.createDummyContract throw error if type is invalid (#2188) (@catloversg)
- Fix: Documentation navigator does not handle external URL properly (#2202) (@catloversg)
- Detect circular dependencies when generating modules (#2194) (@catloversg)
- Fix: Running scripts may be loaded before main UI (#1726) (@catloversg)
- Fix: Autocomplete of "connect" command does not list purchased servers (#2229) (@gmcew)
- Expose difficulty value of coding contract in NS API (#2230) (@gmcew)
- Fix: Exporting game via menu of Steam app does not give export bonus (#2241) (@catloversg)
- Update migration instruction for breaking change of ns.nFormat (#2247) (@catloversg)
- Fix: Loading code discards entire Go data due to missing migration code for favor/rep (#2252) (@catloversg)
- Add removal of API server to list of breaking changes (#2205) (@catloversg)
- Fix typo in KARMA_1000000 achievement (#2264) (@UncleCeiling)
- Clarify the reason of failure when trying to move a running script (#2160) (@UncleCeiling)
- Fix: ns.mv writes to destination file even if it cannot delete source file (#2267) (@catloversg)
- Update messages related to text files (#2266) (@catloversg)
- Fix calculateExp so that it won't return a too small result (#2274) (@d0sboots)
- Change ns.alert to accept multiple args as other print functions (#2278) (@vamo89)
- Fix: Coding contract can be solved manually via UI after it is removed on prestige (#2281) (@TheCleric)
- Ensure ns.go.analysis.getValidMoves correctly handles playing as white (#2292) (@ficocelliguy)
- Ensure that player's promises are changed to "gameOver" once the game is over (#2198) (@ficocelliguy)
- Export save data before migrating to v3 (#2304) (@catloversg)
- Prevent tiny islands surrounded by offline nodes during initial board generation (#2310) (@ficocelliguy)
- Add FragmentType to NS Enums (#2341) (@TheCleric)
- Do not round down amount of hacked money in "hack" CLI (#2344, #2345) (@catloversg)
- Rename "TIX" interface to "Stock" (#2351) (@catloversg)
- Add --temporary flag to run command (#2354) (@catloversg)
- Let ServerProfiler.exe autocomplete servers (#2356) (@rladenson)
- Correct phrasing in SmartSonar description (#2368) (@The-Chaddeus)
- Make ActiveFragment extend Fragment (#2373) (@catloversg)
- Expose theme as css custom props (#2380) (@shyguy1412)
- Support css file type (#2378) (@shyguy1412)
- Do not update captures on passed analysis boards (#2415) (@ficocelliguy)
- Update error message when backdooring without admin rights (#2435) (@CicaProductions)
- Add literature expanding the augment prestige lore (#2433) (@Nick-Colclasure)
- Improve script args validation message (#2451) (@ficocelliguy)
- Add sleeve commands to purchase sleeves and memory via the API (#2443) (@TheAimMan)
- Allow ns.read to read .msg and .lit files (#2455) (@catloversg)
- Fix scoring of very large open areas in IPvGO (#2464) (@ficocelliguy)
- Make threads' bonus apply to timing attack puzzle's extra time per char (#2466) (@ficocelliguy)
- Improve documentation; remove requirement that getBlockedRam and getDepth be called on a darknet server (#2472) (@ficocelliguy)
- Prevent blocked ram on the "darkweb" server (#2468) (@ficocelliguy)
- Add extra hint to sorted echo puzzle at high levels (#2465) (@ficocelliguy)
- Change coding contract's fallback reward priorities (#2481) (@gmcew)
- Add JS object properties as server names; refactor save/load/server storage to support this (#2482) (@ficocelliguy)
- Buff packet sniffing slightly (#2485) (@d0sboots)
- Fix: Darknet state is not reset properly on prestige (#2486) (@catloversg)
- Change how coding contract rewards are randomized (#2490) (@catloversg)
- Expose ProgramName enum (#2492) (@catloversg)
- More fixes and feedback in darknet (#2489) (@ficocelliguy)
- Fix missed cases in offline server handling (#2495) (@d0sboots)
- Adjust darknet balance from player feedback (#2512) (@ficocelliguy)
- Add "Find Largest Rectangle in a Matrix" coding contract (#2519) (@Misha279-UA)
- Tweak Dnet based on player feedback (#2533, #2545, #2593) (@ficocelliguy)
- Allow parsing unknown options with data.flags in autocomplete (#2539) (@catloversg)
- Fix webstorm by using a mutationLock (#2542) (@d0sboots)
- Print error message when calling ns.ui.closeTail with nonexistent pid or pid of stopped scripts (#2557) (@catloversg)
- Add minimum width/height constraints to ns.ui.resizeTail (#2558) (@catloversg)
- Add API to minimize and expand tail windows (#2556) (@catloversg)
- Improve error messages for invalid sleeve numbers (#2567) (@catloversg)
- Improve help text of expr command (#2561) (@catloversg)
- Rework faction rumor (#2569) (@catloversg)
- Fix: hacknetNodeCost formula API throws when using documented optional parameter (#2577) (@catloversg)
- Rework intelligence override (#2575) (@catloversg)
- Electron: Allow opening dev tools via CLI arguments (#2589) (@catloversg)
- Support importing Steam Cloud save file manually (#2583) (@catloversg)
- Electron: Add UI menus and CLI flags to change log levels (#2596) (@catloversg)
- Import correct cloud file when multiple exist (#2599) (@catloversg)
- Dnet: Remove packet capture (#2594) (@ficocelliguy)
- Generate more frequent and lower-reward coding contracts (#2603) (@ficocelliguy)
- Electron: Fix issues in edge cases of using --export-save (#2590) (@catloversg)
- Fix recursive alias detection causing infinite recursion (#2610) (@lstutzman)
- Add "hidden" mkdir command (#2646) (@catloversg)
- Dnet: Remove bonus time effect on authentication and heartbleed speed; fix ram rounding (#2627) (@ficocelliguy)
- Fix tab completion for multi-word quoted autocomplete options (#2612) (@lstutzman)
- Add weakenEffect to formulas.hacking namespace (#2626) (@lstutzman)
- Update description of "cat" in "help" command (#2654) (@catloversg)
- Reduce achievements check interval (#2650) (@catloversg)
- Fix: calculateExp throws errors in edge cases (#2667) (@catloversg)
- Clear recent scripts when installing augmentations (#2670) (@Mathekatze)
- Better error message for port serialization failure (#2688) (@d0sboots)
- Adjust charisma augmentation power (#2698) (@ficocelliguy)
- Clarify what "backdoor" does in "help" command (#2694) (@abbyintheattic)
- Fix: Hacknet upgrade cost formulas ignore upgrade level when enforcing max level (#2696) (@lucebac)
- Improve error messages for invalid module parsing (#2707) (@catloversg)
### DOCUMENTATION
- Fix wrong links in some docs pages (#2082) (@mizmantle)
- Add getContractTypes to Coding Contracts documentation page (#2089) (@gmcew)
- Clarify applicability of wiki entry in Hamming contracts (#2087) (@gmcew)
- Make minor improvements in "Getting Started" page (#2112) (@catloversg)
- Remove mention of Netscript 1.0/2.0/JS in NetscriptDefinitions.d.ts (#2150) (@catloversg)
- Clarify ns.go.getGameState (#2158) (@Arjan-akkermans)
- Remove unnecessary br tag in scripts.md (#2204) (@catloversg)
- Fix minor problem in old changelog (#2203) (@catloversg)
- Remove mention of NS2 in NetscriptDefinitions.d.ts (#2200) (@catloversg)
- Move CONTRIBUTING.md (#2191) (@catloversg)
- Specify parameter types of of ns.go.analysis.highlightPoint and ns.go.analysis.clearPointHighlight (#2175) (@kevinsandow)
- Fix warnings when generating NS API docs (#2189) (@kevinsandow)
- Support showing images in markdown docs (#2207) (@catloversg)
- Update links to CONTRIBUTING.md (#2213) (@catloversg)
- Update contribution guide (#2214) (@emmanuel-ferdman)
- Clarify condition of joining Daedalus faction (#2234) (@catloversg)
- Update infiltration docs (#2259) (@catloversg)
- Mention TypeScript support and update example in React docs (#2263) (@catloversg)
- Update Remote API docs (#2258) (@catloversg)
- Change ns.tail to ns.ui.openTail in example code (#2270) (@gmcew)
- Clarify description of ArrayJumpingGame contract (#2277) (@acidduk)
- Update 'Hacking Algorithms' page (#2288) (@gmcew)
- Reword HammingCode contracts and mention dummy contract API (#2296) (@gmcew)
- Clarify cross-host characteristic of PID and port (#2336) (@Thaccus)
- Fix wrong commands in getting_started.md (#2349) (@catloversg)
- Use alternative fix for newline issue in IPvGO docs (#2350) (@catloversg)
- Fix missing/wrong TSDoc of APIs having optional host parameter (#2371) (@catloversg)
- Fix typo in README.md (#2375) (@HolyNaet)
- Improve wording around host argument of getScriptRam (#2374) (@nickmshelley)
- Remove references to defunct wiki from Hack/Weaken/Grow (#2404) (@maglinvinn)
- Clarify behavior of NS.getScriptLogs() for running scripts (#2408) (@sg673)
- Add instructions to troubleshoot common issues for contributors to CONTRIBUTING.md (#2420) (@catloversg)
- Add FAQ and another JIT batcher illustration to documentation (#2400) (@HolyNaet)
- Change wording in Singularity.installAugmentation (#2439) (@HolyNaet)
- Update README.md to correct Frequently Asked Questions link. (#2444) (@jonathonchase)
- Add a question and a grammatical fix in faq.md (#2446) (@HolyNaet)
- Move current changelog to changelog-v2.md (#2461) (@catloversg)
- Fix some IPvGO docs that do not reflect winstreak rep converted to favor (#2463) (@ficocelliguy)
- Add glossary of darknet terms to the documentation page (#2469) (@ficocelliguy)
- Add a note to CONTRIBUTING.md about the npm peer dep issue (#2474) (@d0sboots)
- Update darknet documentation (#2483) (@ficocelliguy)
- Fix invalid links in ns.sleep and ns.asleep (#2496) (@catloversg)
- Use relative links instead of absolute links (#2521) (@catloversg)
- Document quirky behavior of ns.flags when default value is nullish (#2528) (@catloversg)
- Clarify how share power affects reputation gain rate of non-hacking work (#2544) (@catloversg)
- Update guides (#2550) (@catloversg)
- Add missing newline after RAM cost (#2570) (@catloversg)
- Update mention of outdated getStockForecast API (#2578) (@catloversg)
- Fix newline issues in IPvGO docs and add missing RAM cost (#2602) (@catloversg)
- Document coding contract's generation and rewards (#2624) (@catloversg)
- Clarify scp and exec darknet permissions in API docs (#2634) (@lstutzman)
- Update RAM cost of hacknet APIs and remove unnecessary RAM cost docs (#2639) (@catloversg)
- Update tutorial script for buying cloud servers (#2653) (@catloversg)
- Improve coding contract documentation (#2689) (@catloversg)
### SPOILER CHANGES - UI
- Fix: BitVerse does not show all BN multipliers in some cases (#2045) (@catloversg)
- Add UI hint about scripting tea/party and using Intern (#2179) (@catloversg)
- Fix: Bladeburner console prints main body's HP instead of sleeve's HP (#2390) (@catloversg)
- Reduce threshold of showing warning of low population (#2450) (@catloversg)
- Fix: Hacknet server UI shows NaN hash rate when 100% RAM is being used (#2500) (@catloversg)
- Prevent ending BNs through reuse of Bladeburner UI event handler (#2574) (@catloversg)
- Always show Black Operations list (#2592) (@catloversg)
- Show hints of Sleeves mechanic in pre-endgame (#2605) (@catloversg)
- Consistently calculate BitNode "level" (#2645) (@catloversg)
- Add tooltips explaining why Bladeburner skill upgrades are disabled (#2648) (@catloversg)
- Add button to open Faction page from Gang UI (#2655) (@catloversg)
- Ensure intelligence override is a positive integer (#2673) (@catloversg)
- Remove max width of corporation division list (#2686) (@catloversg)
### SPOILER CHANGES - MISC
- Add achievement for acquiring SF13.1 (#2107) (@catloversg)
- Add achievement for completing all BNs (#2128) (@catloversg)
- Renamed Division.type to Division.industry (#2079, #2152) (@whiskeyfur, @catloversg)
- Make ns.hacknet.spendHashes handle invalid targets in same way as UI (#2102) (@catloversg)
- Add achievements for BN14 (#2140) (@catloversg)
- Print logs when ns.hacknet.spendHashes fails and update param type of APIs using hash upgrade (#2145) (@catloversg)
- Add ns.singularity.getUnlockedAchievements (#2156) (@UncleCeiling)
- Remove mention of unusable research "sudo.Assist" (#2187) (@catloversg)
- Change message of Singularity error and uncaught promise error (#2174) (@catloversg)
- Add ns.singularity.getHackingLevelRequirementOfProgram (#2271) (@catloversg)
- Expose gang's discount rate (#2272) (@catloversg)
- Prevent purchasing product-only research for material industries (#2283) (@catloversg)
- Fix: Stat levels are not recalculated after grafting augs or accepting Stanek's Gift (#2322) (@catloversg)
- Expose production limit of material and product (#2330) (@AnteIndustrial)
- Make some editorial changes in Black Operations' description (#2449) (@catloversg)
- Add warning when installing backdoor on backdoored server with Singularity API (#2458) (@catloversg)
- Fix: Sleeves can earn exp and purchase augmentations when enabling disableSleeveExpAndAugmentation (#2467) (@catloversg)
- Add improved challenge achievement for BN15 (#2479) (@ficocelliguy)
- Stop randomizing Bladeburner's action difficulty (#2491) (@catloversg)
- Adjusted Bladeburner's team bonus computation to make one member help (#2541) (@JoshuaCF)
- Rebalance charisma exp gain of Recruitment action (#2549) (@catloversg)
- Add APIs to get rank gain and rank loss of an action (#2572) (@catloversg)
- Reduce RAM cost of inGang and inBladeburner APIs (#2582) (@catloversg)
- Fix skillMaxUpgradeCount returning 1 at extreme skill levels (#2611) (@lstutzman)
- API: Expose charged effects of Stanek's Gift active fragments (#2638) (@catloversg)
- Apply SF override to charisma calculations (#2642) (@catloversg)
- Update description of "BN9: Challenge" achievement (#2647) (@catloversg)
- Fix: Intelligence data is incorrectly migrated when Intelligence is not unlocked (#2660, #2666) (@catloversg)
- Restrict team count of Ops/BlackOps to total team size (#2672) (@catloversg)
- Fix: DarkscapeNavigator program is not granted on BN prestige when having SF15 (#2690) (@catloversg)
- Prevent duplicate processing of boost materials (#2695) (@catloversg)
- Update error message of ns.singularity.joinFaction (#2700) (@catloversg)
- Update RAM cost of nextUpdate and similar APIs (#2702) (@catloversg)
- Update description of "You and what army?" achievement (#2703) (@catloversg)
- Rework material production limit (#2683) (@catloversg)
- Remove references to "sudo.Assist" research (#2709) (@catloversg)
### SPOILER CHANGES - DOCUMENTATION
- Update rewards of SF14 (#2142) (@catloversg)
- Fix missing and outdated info of optional parameter (#2176) (@catloversg)
- Specify param type of many Corporation APIs (#2190) (@catloversg)
- Fix typo in bitnodes.md (#2221) (@catloversg)
- Add short guide of BitNode recommendation (#2041) (@1337JiveTurkey)
- Add comprehensive guide of BitNode recommendation (#2044) (@catloversg)
- Fix outdated description of Bladeburner interface (#2226) (@catloversg)
- Clarify advice about Smart Supply in round 1 (#2233) (@catloversg)
- Fix wrong param/return type and clarify ns.sleep, ns.asleep, ns.singularity.exportGame (#2242) (@catloversg)
- Clarify ns.gang.getOtherGangInformation (#2289) (@acidduk)
- Update type of Player.factions, GangGenInfo.faction and CorpMaterialConstantData.name (#2347) (@catloversg)
- Clarification of Singularity costs inside BN4 (#2403) (@gmcew)
- Update math notations in corporation docs (#2452) (@catloversg)
- Update BitNode recommendation short guide (#2523, #2529) (@catloversg)
### CODEBASE/REFACTOR/WORKFLOW/JEST/TOOL/DEPS
- Electron: Use steamworks.js to integrate with Steamworks SDK (#1563) (@catloversg)
- Electron: Replace deprecated protocol.interceptFileProtocol with protocol.handle (#2100) (@catloversg)
- Dev menu: Add tools to set stat level and queue augmentations of faction (#2118) (@catloversg)
- Remove wrong and irrelevant comments/code (#2111) (@catloversg)
- Refactor: Change repNeededToDonate to favorNeededToDonate (#2134) (@d0sboots)
- Workflow: Build artifacts and upload to release (#2120) (@catloversg)
- Configure .editorconfig to not trim trailing whitespace in NetscriptDefinitions.d.ts (#2137) (@G4mingJon4s)
- Fix typo of "CorruptableText" (#2144) (@catloversg)
- Dev menu: Fix bugs in WD tools (#2141) (@catloversg)
- Make prettier ignore src/Documentation/pages.ts (#2185) (@catloversg)
- Workflow: Allow enabling dev mode when deploying dev build (#2184) (@catloversg)
- Refactor: Refactor message handler of Remote API (#2211) (@catloversg)
- Dev menu: Auto expand last opened tools (#2182) (@catloversg)
- Update build script to avoid unnecessary newline in generated script (#2224) (@catloversg)
- Fix inconsistent generated pages.ts (#2236) (@catloversg)
- Dev menu: Reset hacknet server list properly when setting level of SF9 (#2177) (@catloversg)
- Update dev dependencies (#2246) (@catloversg)
- Move all docs into en/ subdirectory (#1505) (@d0sboots)
- Fix ctrl-clicking after the doc_en refactor (#2256) (@d0sboots)
- Electron: Specify mime type when loading wasm files (#2262) (@catloversg)
- Electron: Build universal macOS binary (#2306) (@Snarling)
- Add Jest test to test migration from v2 to v3 (#2305) (@catloversg)
- Workflow: Split build-artifacts.yml into 2 workflows (#2309) (@catloversg)
- Workflow: Update NodeJs version in workflows (#2319) (@catloversg)
- Update NodeJS to v22 (#2318) (@mctylr-gh)
- Update api-documentor and api-extractor (#2320) (@mctylr-gh)
- Improve dev menu for augmentations (#2315) (@d0sboots)
- Add more debug info for troubleshooting corporation issues (#2327) (@catloversg)
- Update doc.sh (#2331) (@catloversg)
- Enable restoreMocks option and fix lint errors (#2333) (@catloversg)
- Workflow: Add new job to check generated docs (#2329) (@catloversg)
- Quiet Jest tests output on expected failures (#2332) (@mctylr-gh)
- Standardize error handling in netscriptGoImplementation.ts (#2335) (@ficocelliguy)
- Enable linting in test folder (#2337) (@catloversg)
- Update minor version of material UI and Emotion packages (#2339) (@mctylr-gh)
- Refactor Stanek's Gift UI code and change internal FragmentType enum (#2346) (@catloversg)
- Minor React related packages update (#2353) (@mctylr-gh)
- Remove redundant check in getServer utility function and serverExists API (#2357) (@catloversg)
- Refactor tests that add home server manually (#2358) (@catloversg)
- Update Electron version (#2360) (@catloversg)
- Prepare for save data migration of next beta version (#2369) (@catloversg)
- Rewrite infiltration to pull state out of React (#2316, #2393) (@d0sboots)
- Use getCoreBonus function when calculating server growth (#2387) (@aaaa-imcute)
- Refactor implementation of ports (#2396) (@d0sboots)
- Avoid re-entrancy issues with EventEmitter (#2397) (@d0sboots)
- Update packages-lock.json to not use vulnerable versions (#2405) (@mctylr-gh)
- Allow specifying migrator when migrating player's scripts (#2418) (@catloversg)
- Refactor code related to in-game documentation link (#2422) (@catloversg)
- Clean up "doc" folder (#2427) (@catloversg)
- Update dependencies related to monaco (#2432) (@catloversg)
- Fix typos and duplicating ms per cycle constant (#2436) (@gmcew)
- Update dependencies to fix vulnerability warnings (#2445) (@catloversg)
- Move Result to the public API (#2398) (@d0sboots)
- Generate display data for math notation at built time and remove runtime mathjax dependency (#2447) (@catloversg)
- Update SaveData type to be compatible with TS 5.9 and upgrade TS (#2457) (@catloversg)
- Update NodeJS to v24 (#2456) (@catloversg)
- Update format created by "build artifacts" workflows (#2470) (@Snarling)
- Consolidate checks under getFailureResult() (#2476) (@d0sboots)
- Fix: Global states are not reset properly between each Jest test (#2487) (@catloversg)
- Change getFailureResult to checkDarknetServer (#2494) (@d0sboots)
- Speed up by-ip lookups by introducing a new map entry (#2488) (@d0sboots)
- Refactor/adjust getPixelPosition in darknet UI code (#2501) (@d0sboots)
- Add tests for checking getAnswer and solver of coding contracts (#2503) (@catloversg)
- Refactor ImportSave component (#2505) (@catloversg)
- Show coding contract names when their tests failed (#2520) (@catloversg)
- Workflow: Fix wrong instruction of generating docs (#2522) (@catloversg)
- Update comment of LoadingScreen of ComplexPage enum (#2527) (@catloversg)
- Make getPlayer 10x faster (#2548) (@d0sboots)
- Create monaco editor instance with null model (#2563) (@catloversg)
- Update dependencies (#2576) (@catloversg)
- Remove barrel imports in Bladeburner code (#2580) (@catloversg)
- Fix React warning in IPvGO scoring explanation popup (#2581) (@catloversg)
- Update Babel core, presets and module loader for webpack (#2585) (@catloversg)
- Add script to generate webpack bundle report (#2587) (@catloversg)
- Update Electron (#2591) (@catloversg)
- Mitigate issue of forcefullyCrashRenderer (#2597) (@catloversg)
- Split Settings.ts to reduce number of imports (#2600) (@catloversg)
- Remove duplicate random alphanumeric string functions (#2601) (@catloversg)
- Update comments to reflect changes in #2603 (#2606) (@catloversg)
- Replace ipExists() linear scan with O(1) Map.has() (#2621) (@lstutzman)
- Refactor and fix issues in db.ts (#2623) (@catloversg)
- Add dependency array to TerminalInput keydown useEffect (#2620) (@lstutzman)
- Add dependency array to GameRoot useEffect (#2617) (@lstutzman)
- Dev menu: Initialize dark net data when setting SF15 level (#2632) (@catloversg)
- Use type-only imports in ArrayHelpers.ts (#2630) (@catloversg)
- Remove redundant "$" from JS/TS regex in webpack config (#2649) (@catloversg)
- Allow specifying commit hash id when building artifacts (#2652) (@catloversg)
- Fix passive event listener warning (#2671) (@catloversg)
- Ignore .DS_Store files when generating pages.ts (#2685) (@catloversg)
- Consistently check when to show intelligence skill (#2692) (@catloversg)
- Fix api-extractor warnings (#2701) (@catloversg)
- Add comments explaining redundant check in product calculation (#2705) (@catloversg)
`,
} as const;
+14 -5
View File
@@ -12,6 +12,7 @@ import type { DarknetServer } from "../../Server/DarknetServer";
import { resolveCacheFilePath } from "../../Paths/CacheFilePath";
import type { CacheResult } from "@nsdefs";
import { addClue, cctCooldownReached } from "./effects";
import { getBitNodeMultipliers } from "../../BitNode/BitNode";
export const generateCacheFilename = (isPhishingCache: boolean, prefix?: string) => {
const filenamePrefix = prefix ?? cachePrefixes[Math.floor(Math.random() * cachePrefixes.length)];
@@ -41,11 +42,15 @@ export const getRewardFromCache = (server: DarknetServer, cacheName: string, sup
};
}
const rewards = [getMoneyReward, getProgramAndStockMarketRelatedRewards, getStockReward, getDataFileReward];
const rewards = [getProgramAndStockMarketRelatedRewards, getStockReward, getDataFileReward];
if (cacheName.endsWith(".d.cache")) {
// only include ccts from caches generated from phishing attacks
rewards.push(getCCTReward);
}
if (getBitNodeMultipliers(Player.bitNodeN, 1).DarknetMoneyMultiplier) {
// only include money reward if it is not disabled by the bn mults
rewards.push(getMoneyReward);
}
const reward = rewards[Math.floor(Math.random() * rewards.length)];
const result = reward(difficulty, server);
@@ -98,10 +103,14 @@ export const getStockReward = (difficulty: number): string => {
initStockMarket();
}
const stockSymbols = Object.keys(StockSymbol);
const randomStock = stockSymbols[Math.floor(Math.random() * stockSymbols.length)];
const shares = Math.floor(1 + difficulty * 5 + Math.random() * 10);
StockMarket[randomStock].playerShares += shares;
return `You have discovered a stock option cache containing ${shares} shares of ${randomStock}!`;
const stock = StockMarket[stockSymbols[Math.floor(Math.random() * stockSymbols.length)]];
const maxNewShares = stock.maxShares - stock.playerShares - stock.playerShortShares;
if (maxNewShares <= 0) {
return getMoneyReward(difficulty);
}
const shares = Math.min(Math.floor(1 + difficulty * 5 + Math.random() * 10), maxNewShares);
stock.playerShares += shares;
return `You have discovered a stock option cache containing ${shares} shares of ${stock.symbol}!`;
};
export const getDataFileReward = (difficulty: number, server: DarknetServer): string => {
+1 -1
View File
@@ -270,7 +270,7 @@ export const handleLabyrinthPassword = (
return {
passwordAttempted: attemptedPassword,
code: ResponseCodeEnum.Success,
message: "You have discovered the end the labyrinth.",
message: "You have discovered the end of the labyrinth.",
data: labServer.password,
};
}
+13 -4
View File
@@ -18,6 +18,7 @@ import { hasFullDarknetAccess } from "../effects/effects";
import { getFriendlyType, TypeAssertionError } from "../../utils/TypeAssertion";
import { isIPAddress } from "../../Types/strings";
import { roundToTwo } from "../../utils/helpers/roundToTwo";
import { safelyReverseString } from "../../utils/StringHelperFunctions";
export type PasswordResponse = {
code: DarknetResponseCode;
@@ -158,11 +159,11 @@ const decorateName = (name: string): string => {
const connector = connectors[Math.floor(Math.random() * connectors.length)];
if (Math.random() < 0.3) {
updatedName = l33tifyName(name);
updatedName = l33tifyName(updatedName);
}
if (Math.random() < 0.05) {
updatedName = updatedName.split("").reverse().join("");
updatedName = safelyReverseString(updatedName);
}
if (Math.random() < 0.1) {
@@ -180,7 +181,11 @@ const decorateName = (name: string): string => {
}
} while (GetServer(updatedName) !== null);
return updatedName;
// Defensive coding. All operations above preserve well-formed UTF-16, so this is currently redundant. It's a
// safeguard to ensure the function never returns ill-formed UTF-16 if future changes introduce code unitlevel
// manipulation.
// This normalization is lossy (lone surrogates -> U+FFFD).
return updatedName.toWellFormed();
};
const l33tifyName = (name: string): string => {
@@ -191,7 +196,11 @@ const l33tifyName = (name: string): string => {
const replacement: string = l33t[char] ?? "";
updatedName = updatedName.replaceAll(char, replacement);
}
return updatedName;
// Defensive coding. All operations above preserve well-formed UTF-16, so this is currently redundant. It's a
// safeguard to ensure the function never returns ill-formed UTF-16 if future changes introduce code unitlevel
// manipulation.
// This normalization is lossy (lone surrogates -> U+FFFD).
return updatedName.toWellFormed();
};
const getMaxRam = (difficulty: number): number => {
+1 -1
View File
@@ -39,7 +39,7 @@ Once you have enough ports opened on a [server](servers.md) and have ran the NUK
### For specific details of how Hacking work "offline"
See [Offline And Bonus Time](../advanced/offlineandbonustime.md).
See [Offline And Bonus Time](../programming/offlineandbonustime.md).
## General Hacking Mechanics
+1 -1
View File
@@ -17,7 +17,7 @@ Scripts can be run on any [server](servers.md) you have root access to, but not
Being actual JavaScript, Bitburner also contains some quirks and limitations.
For this reason, it is not possible for Bitburner scripts to run the same way at all times.
However, you will continue to earn money and exp when Bitburner is not running, though at a slower rate.
See [How Scripts Work Offline](../advanced/offlineandbonustime.md) for more details.
See [How Scripts Work Offline](../programming/offlineandbonustime.md) for more details.
## Identifying a Script
@@ -377,7 +377,7 @@ Paste the following code into the [Script](../basic/scripts.md) editor:
// amount of servers
while (i < ns.cloud.getServerLimit()) {
// Check if we have enough money to purchase access to a server
if (ns.getServerMoneyAvailable("home") > ns.cloud.getRamLimit(ram)) {
if (ns.getServerMoneyAvailable("home") > ns.cloud.getServerCost(ram)) {
// If we have enough money, then:
// 1. Purchase the server
// 2. Copy our hacking script onto the newly purchased cloud server
+1 -1
View File
@@ -28,8 +28,8 @@
## Advanced Mechanics
- [Hacking algorithms](programming/hackingalgorithms.md)
- [Offline scripts and bonus time](programming/offlineandbonustime.md)
- [List of factions and their requirements](advanced/faction_list.md)
- [Offline scripts and bonus time](advanced/offlineandbonustime.md)
- [BitNodes](advanced/bitnodes.md)
- [BitNode recommendation - Short guide](advanced/bitnode_recommendation_short_guide.md)
- [BitNode recommendation - Comprehensive guide](advanced/bitnode_recommendation_comprehensive_guide.md)
@@ -25,7 +25,7 @@ In some cases, the only way to get to deeper parts of the net is to hitch a ride
- `await ns.dnet.authenticate(hostname, password)` lets you guess and check passwords for servers directly connected to your script's server. If you guess right, you get admin access and can use `exec` and `scp` to move scripts onto that server.
- Some servers require interactive feedback to guess their password. Use `await ns.dnet.heartbleed(hostname)` to check that server's logs and get clues after you attempt a password.
- `ns.dnet.connectToSession(hostName, password)` lets you use a password you already know to log in to a darknet server at a distance. This is required to scp files there.
- Some servers will have part of their max ram blocked off. Use `ns.dnet.influence.memoryReallocation()` to free it.
- Some servers will have part of their max ram blocked off. Use `ns.dnet.memoryReallocation()` to free it.
- Some servers have valuable .cache files you can open with `ns.dnet.openCache(fileName)`
- Darknet servers allow you to run `ns.dnet.phishingAttack()` to get money or .cache files based off of your charisma and crime success stat.
- Using `ns.dnet.setStasisLink()` will stasis lock the current server. This prevents it from moving or going offline, and also allows getting a session on the server at a distance like backdooring does.
@@ -248,9 +248,10 @@ const authenticateWithNoPassword = async (ns, hostname) => {
return result.success;
};
// This lets you tab-complete putting "--tail" on the run command so you can see the script logs as it runs, if you want
// If you add support to the script to take other arguments, you can add them here as well for convenience
export function autocomplete(data: AutocompleteData) {
/** This lets you tab-complete putting "--tail" on the run command so you can see the script logs as it runs, if you want
* If you add support to the script to take other arguments, you can add them here as well for convenience
* @param {AutocompleteData} data */
export function autocomplete(data) {
return ["--tail"];
}
```
@@ -37,7 +37,7 @@ Common infinite loop when translating the server purchasing script in starting g
let i = ns.cloud.getServerNames().length;
while (i < ns.cloud.getServerLimit()) {
if (ns.getServerMoneyAvailable("home") > ns.cloud.getRamLimit(ram)) {
if (ns.getServerMoneyAvailable("home") > ns.cloud.getServerCost(ram)) {
const hostname = ns.cloud.purchaseServer("cloud-server-" + i, ram);
ns.scp("early-hack-template.js", hostname);
ns.exec("early-hack-template.js", hostname, 3);
@@ -24,4 +24,4 @@ Also, note that because of the way the JavaScript engine works, whenever you rel
Because of the above details, some activities in Bitburner accumulate "Bonus Time" while the game is closed or in an inactive browser tab. For mechanics that have a Bonus Time effect, the rate of the associated activity or task is significantly increased.
For example, if a certain [Bladeburner](bladeburners.md) contract requires 15 seconds to complete under normal conditions, the same task will be finished instead in 3 seconds if the Bonus Time effect is 5x. The specific details and effects of Bonus Time vary by mechanic.
For example, if your opponent in [IPvGO](../programming/go_algorithms.md) takes 1 second to play a move under normal conditions, it will only take 200ms if the Bonus Time effect is 5x. The specific details and effects of Bonus Time vary by mechanic.
@@ -13,11 +13,13 @@ All these tools support synchronizing scripts to Bitburner and transpiling TypeS
Links:
- https://github.com/bitburner-official/typescript-template
- https://github.com/Tanimodori/viteburner
- https://github.com/shyguy1412/bb-external-editor
- [typescript-template](https://github.com/bitburner-official/typescript-template): A template for synchronizing Typescript/Javascript from your computer to the game.
- [viteburner](https://github.com/Tanimodori/viteburner): Daemon tools of bitburner using vite for script transform, file syncing, RAM monitoring and more!
- [bb-external-editor](https://github.com/shyguy1412/bb-external-editor): This tool uses esbuild to transpile and bundle your scripts. It supports JS, TS and React as well as importing from any browser-compatible npm library out of the box.
- [BitburnerGoFilesync](https://github.com/CTNOriginals/BitburnerGoFilesync): A standalone binary cli tool that doesn't require any setup or third party libraries. It is designed to be very minimal and easy to use out of the box.
`typescript-template` has a small set of options and features. Its simplicity is by design. `viteburner` and `bb-external-editor` have more fancy features.
`typescript-template` and `BitburnerGoFilesync` both have a small set of options and features, their simplicity is by design.
`viteburner` and `bb-external-editor` have more fancy features and may offer more control for specific use cases.
## Troubleshooting tips
+76 -76
View File
@@ -26,44 +26,44 @@ import file23 from "./doc/en/advanced/gang.md?raw";
import file24 from "./doc/en/advanced/grafting.md?raw";
import file25 from "./doc/en/advanced/hacknetservers.md?raw";
import file26 from "./doc/en/advanced/intelligence.md?raw";
import file27 from "./doc/en/advanced/offlineandbonustime.md?raw";
import file28 from "./doc/en/advanced/sleeves.md?raw";
import file29 from "./doc/en/advanced/sourcefiles.md?raw";
import file30 from "./doc/en/advanced/stanek.md?raw";
import file31 from "./doc/en/basic/augmentations.md?raw";
import file32 from "./doc/en/basic/autocomplete.md?raw";
import file33 from "./doc/en/basic/codingcontracts.md?raw";
import file34 from "./doc/en/basic/companies.md?raw";
import file35 from "./doc/en/basic/crimes.md?raw";
import file36 from "./doc/en/basic/factions.md?raw";
import file37 from "./doc/en/basic/hacking.md?raw";
import file38 from "./doc/en/basic/hacknet_nodes.md?raw";
import file39 from "./doc/en/basic/infiltration.md?raw";
import file40 from "./doc/en/basic/programs.md?raw";
import file41 from "./doc/en/basic/ram.md?raw";
import file42 from "./doc/en/basic/reputation.md?raw";
import file43 from "./doc/en/basic/scripts.md?raw";
import file44 from "./doc/en/basic/servers.md?raw";
import file45 from "./doc/en/basic/stats.md?raw";
import file46 from "./doc/en/basic/stockmarket.md?raw";
import file47 from "./doc/en/basic/terminal.md?raw";
import file48 from "./doc/en/basic/world.md?raw";
import file49 from "./doc/en/changelog-v0.md?raw";
import file50 from "./doc/en/changelog-v1.md?raw";
import file51 from "./doc/en/changelog-v2.md?raw";
import file52 from "./doc/en/changelog.md?raw";
import file53 from "./doc/en/help/faq.md?raw";
import file54 from "./doc/en/help/getting_started.md?raw";
import file55 from "./doc/en/help/tools_and_resources.md?raw";
import file56 from "./doc/en/index.md?raw";
import file57 from "./doc/en/migrations/ns2.md?raw";
import file58 from "./doc/en/migrations/v1.md?raw";
import file59 from "./doc/en/migrations/v2.md?raw";
import file60 from "./doc/en/programming/darknet.md?raw";
import file61 from "./doc/en/programming/game_frozen.md?raw";
import file62 from "./doc/en/programming/go_algorithms.md?raw";
import file63 from "./doc/en/programming/hackingalgorithms.md?raw";
import file64 from "./doc/en/programming/learn.md?raw";
import file27 from "./doc/en/advanced/sleeves.md?raw";
import file28 from "./doc/en/advanced/sourcefiles.md?raw";
import file29 from "./doc/en/advanced/stanek.md?raw";
import file30 from "./doc/en/basic/augmentations.md?raw";
import file31 from "./doc/en/basic/autocomplete.md?raw";
import file32 from "./doc/en/basic/codingcontracts.md?raw";
import file33 from "./doc/en/basic/companies.md?raw";
import file34 from "./doc/en/basic/crimes.md?raw";
import file35 from "./doc/en/basic/factions.md?raw";
import file36 from "./doc/en/basic/hacking.md?raw";
import file37 from "./doc/en/basic/hacknet_nodes.md?raw";
import file38 from "./doc/en/basic/infiltration.md?raw";
import file39 from "./doc/en/basic/programs.md?raw";
import file40 from "./doc/en/basic/ram.md?raw";
import file41 from "./doc/en/basic/reputation.md?raw";
import file42 from "./doc/en/basic/scripts.md?raw";
import file43 from "./doc/en/basic/servers.md?raw";
import file44 from "./doc/en/basic/stats.md?raw";
import file45 from "./doc/en/basic/stockmarket.md?raw";
import file46 from "./doc/en/basic/terminal.md?raw";
import file47 from "./doc/en/basic/world.md?raw";
import file48 from "./doc/en/changelog-v0.md?raw";
import file49 from "./doc/en/changelog-v1.md?raw";
import file50 from "./doc/en/changelog-v2.md?raw";
import file51 from "./doc/en/changelog.md?raw";
import file52 from "./doc/en/help/faq.md?raw";
import file53 from "./doc/en/help/getting_started.md?raw";
import file54 from "./doc/en/help/tools_and_resources.md?raw";
import file55 from "./doc/en/index.md?raw";
import file56 from "./doc/en/migrations/ns2.md?raw";
import file57 from "./doc/en/migrations/v1.md?raw";
import file58 from "./doc/en/migrations/v2.md?raw";
import file59 from "./doc/en/programming/darknet.md?raw";
import file60 from "./doc/en/programming/game_frozen.md?raw";
import file61 from "./doc/en/programming/go_algorithms.md?raw";
import file62 from "./doc/en/programming/hackingalgorithms.md?raw";
import file63 from "./doc/en/programming/learn.md?raw";
import file64 from "./doc/en/programming/offlineandbonustime.md?raw";
import file65 from "./doc/en/programming/remote_api.md?raw";
import file66 from "./doc/en/programming/typescript_react.md?raw";
@@ -1649,44 +1649,44 @@ AllPages["en/advanced/gang.md"] = file23;
AllPages["en/advanced/grafting.md"] = file24;
AllPages["en/advanced/hacknetservers.md"] = file25;
AllPages["en/advanced/intelligence.md"] = file26;
AllPages["en/advanced/offlineandbonustime.md"] = file27;
AllPages["en/advanced/sleeves.md"] = file28;
AllPages["en/advanced/sourcefiles.md"] = file29;
AllPages["en/advanced/stanek.md"] = file30;
AllPages["en/basic/augmentations.md"] = file31;
AllPages["en/basic/autocomplete.md"] = file32;
AllPages["en/basic/codingcontracts.md"] = file33;
AllPages["en/basic/companies.md"] = file34;
AllPages["en/basic/crimes.md"] = file35;
AllPages["en/basic/factions.md"] = file36;
AllPages["en/basic/hacking.md"] = file37;
AllPages["en/basic/hacknet_nodes.md"] = file38;
AllPages["en/basic/infiltration.md"] = file39;
AllPages["en/basic/programs.md"] = file40;
AllPages["en/basic/ram.md"] = file41;
AllPages["en/basic/reputation.md"] = file42;
AllPages["en/basic/scripts.md"] = file43;
AllPages["en/basic/servers.md"] = file44;
AllPages["en/basic/stats.md"] = file45;
AllPages["en/basic/stockmarket.md"] = file46;
AllPages["en/basic/terminal.md"] = file47;
AllPages["en/basic/world.md"] = file48;
AllPages["en/changelog-v0.md"] = file49;
AllPages["en/changelog-v1.md"] = file50;
AllPages["en/changelog-v2.md"] = file51;
AllPages["en/changelog.md"] = file52;
AllPages["en/help/faq.md"] = file53;
AllPages["en/help/getting_started.md"] = file54;
AllPages["en/help/tools_and_resources.md"] = file55;
AllPages["en/index.md"] = file56;
AllPages["en/migrations/ns2.md"] = file57;
AllPages["en/migrations/v1.md"] = file58;
AllPages["en/migrations/v2.md"] = file59;
AllPages["en/programming/darknet.md"] = file60;
AllPages["en/programming/game_frozen.md"] = file61;
AllPages["en/programming/go_algorithms.md"] = file62;
AllPages["en/programming/hackingalgorithms.md"] = file63;
AllPages["en/programming/learn.md"] = file64;
AllPages["en/advanced/sleeves.md"] = file27;
AllPages["en/advanced/sourcefiles.md"] = file28;
AllPages["en/advanced/stanek.md"] = file29;
AllPages["en/basic/augmentations.md"] = file30;
AllPages["en/basic/autocomplete.md"] = file31;
AllPages["en/basic/codingcontracts.md"] = file32;
AllPages["en/basic/companies.md"] = file33;
AllPages["en/basic/crimes.md"] = file34;
AllPages["en/basic/factions.md"] = file35;
AllPages["en/basic/hacking.md"] = file36;
AllPages["en/basic/hacknet_nodes.md"] = file37;
AllPages["en/basic/infiltration.md"] = file38;
AllPages["en/basic/programs.md"] = file39;
AllPages["en/basic/ram.md"] = file40;
AllPages["en/basic/reputation.md"] = file41;
AllPages["en/basic/scripts.md"] = file42;
AllPages["en/basic/servers.md"] = file43;
AllPages["en/basic/stats.md"] = file44;
AllPages["en/basic/stockmarket.md"] = file45;
AllPages["en/basic/terminal.md"] = file46;
AllPages["en/basic/world.md"] = file47;
AllPages["en/changelog-v0.md"] = file48;
AllPages["en/changelog-v1.md"] = file49;
AllPages["en/changelog-v2.md"] = file50;
AllPages["en/changelog.md"] = file51;
AllPages["en/help/faq.md"] = file52;
AllPages["en/help/getting_started.md"] = file53;
AllPages["en/help/tools_and_resources.md"] = file54;
AllPages["en/index.md"] = file55;
AllPages["en/migrations/ns2.md"] = file56;
AllPages["en/migrations/v1.md"] = file57;
AllPages["en/migrations/v2.md"] = file58;
AllPages["en/programming/darknet.md"] = file59;
AllPages["en/programming/game_frozen.md"] = file60;
AllPages["en/programming/go_algorithms.md"] = file61;
AllPages["en/programming/hackingalgorithms.md"] = file62;
AllPages["en/programming/learn.md"] = file63;
AllPages["en/programming/offlineandbonustime.md"] = file64;
AllPages["en/programming/remote_api.md"] = file65;
AllPages["en/programming/typescript_react.md"] = file66;
+6
View File
@@ -40,6 +40,8 @@ import { CONSTANTS } from "../Constants";
import { BladeburnerConstants } from "../Bladeburner/data/Constants";
import type { PlayerObject } from "../PersonObjects/Player/PlayerObject";
import { CovenantCampaign } from "./ui/CovenantCampaign";
import { GangCampaign } from "./ui/GangCampaign";
import { GangConstants } from "../Gang/data/Constants";
interface FactionInfoParams {
infoText?: JSX.Element;
@@ -812,3 +814,7 @@ export const FactionInfos: Record<FactionName, FactionInfo> = {
},
}),
};
for (const factionName of GangConstants.Names) {
FactionInfos[factionName].campaign = () => <GangCampaign factionName={factionName} />;
}
+7
View File
@@ -7,6 +7,8 @@ import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import { KEY } from "../../utils/KeyboardEventKey";
import { FactionName } from "@enums";
import { canCreateGang } from "../../Gang/helpers";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
interface IProps {
open: boolean;
@@ -33,6 +35,11 @@ export function CreateGangModal(props: IProps): React.ReactElement {
}
function createGang(): void {
const checkResult = canCreateGang(props.facName);
if (!checkResult.success) {
dialogBoxCreate(checkResult.message);
return;
}
Player.startGang(props.facName, isHacking());
props.onClose();
Router.toPage(Page.Gang);
-2
View File
@@ -17,7 +17,6 @@ import { Player } from "@player";
import { Typography, Button } from "@mui/material";
import { FactionWorkType } from "@enums";
import { GangButton } from "./GangButton";
import { FactionWork } from "../../Work/FactionWork";
import { useCycleRerender } from "../../ui/React/hooks";
import { favorNeededToDonate } from "../formulas/donation";
@@ -111,7 +110,6 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea
{faction.name}
</Typography>
<Info faction={faction} factionInfo={factionInfo} />
<GangButton faction={faction} />
{!isPlayersGang && (
<>
{factionInfo.offersWork() && (
-80
View File
@@ -1,80 +0,0 @@
import { Button, Typography, Box, Paper, Tooltip } from "@mui/material";
import React, { useState } from "react";
import { GangConstants } from "../../Gang/data/Constants";
import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
import { Player } from "@player";
import { Faction } from "../Faction";
import { CreateGangModal } from "./CreateGangModal";
interface IProps {
faction: Faction;
}
export function GangButton({ faction }: IProps): React.ReactElement {
const [gangOpen, setGangOpen] = useState(false);
if (
!GangConstants.Names.includes(faction.name) || // not even a gang
!Player.isAwareOfGang() || // doesn't know about gang
(Player.gang && Player.getGangName() !== faction.name) // already in another gang
) {
return <></>;
}
let data = {
enabled: false,
title: "",
tooltip: "" as string | React.ReactElement,
description: "",
};
if (Player.gang) {
data = {
enabled: true,
title: "Manage Gang",
tooltip: "",
description: "Manage a gang for this Faction. Gangs will earn you money and faction reputation",
};
} else {
const checkResult = Player.canAccessGang();
data = {
enabled: checkResult.success,
title: "Create Gang",
tooltip: !checkResult.success ? (
<Typography>Unlocked when reaching {GangConstants.GangKarmaRequirement} karma</Typography>
) : (
""
),
description: "Create a gang for this Faction. Gangs will earn you money and faction reputation",
};
}
const manageGang = (): void => {
// If player already has a gang, just go to the gang UI
if (Player.inGang()) {
return Router.toPage(Page.Gang);
}
setGangOpen(true);
};
return (
<>
<Box>
<Paper sx={{ my: 1, p: 1 }}>
<Tooltip title={data.tooltip}>
<span>
<Button onClick={manageGang} disabled={!data.enabled}>
{data.title}
</Button>
</span>
</Tooltip>
<Typography>{data.description}</Typography>
</Paper>
</Box>
<CreateGangModal facName={faction.name} open={gangOpen} onClose={() => setGangOpen(false)} />
</>
);
}
+110
View File
@@ -0,0 +1,110 @@
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import { Player } from "@player";
import React, { useState } from "react";
import { knowAboutBitverse } from "../../BitNode/BitNodeUtils";
import { GangConstants } from "../../Gang/data/Constants";
import { Router } from "../../ui/GameRoot";
import { Modal } from "../../ui/React/Modal";
import { Page } from "../../ui/Router";
import { FactionName } from "../Enums";
import { CreateGangModal } from "./CreateGangModal";
import { Option } from "./Option";
function GangIncompleteCampaign() {
const [open, setOpen] = useState(false);
return (
<>
<Option
buttonText={"Execute the formation plan"}
infoText={
"The tension between our faction and its rivals has been rising. The leader plans to form a specialized " +
"group under your command to strengthen our position by improving our standing and expanding our resources."
}
onClick={() => setOpen(true)}
></Option>
<Modal open={open} onClose={() => setOpen(false)}>
<Typography component="div">
Each time you attempt to execute the plan, it is abruptly interrupted for reasons no one can explain. You
receive the same distorted message every time:
<br />
<br />
#@)($*&@__Y0U__^%$#@&*()__HAV3__(&@#*$%(@
<br />
()@#*$%(__N0T__@&$#)@*(__S33N__)(*@#&$)(
<br />
@&*($#@&__TH3__#@A&#@*)(@$#@)*
<br />
%$#@&()@__TRU1H__()*@#$&()@#$
</Typography>
</Modal>
</>
);
}
export function GangCampaign({ factionName }: { factionName: FactionName }) {
const [gangOpen, setGangOpen] = useState(false);
if (!GangConstants.Names.includes(factionName)) {
throw new Error(`Cannot create gang with ${factionName}`);
}
if (!knowAboutBitverse()) {
return <GangIncompleteCampaign />;
}
const data = {
enabled: false,
title: "",
tooltip: "" as string | React.ReactElement,
description: "",
};
if (Player.gang) {
if (Player.getGangName() !== factionName) {
data.enabled = false;
data.title = "Create Gang";
data.tooltip = "You already created a gang with another faction";
} else {
data.enabled = true;
data.title = "Manage Gang";
data.description = "Manage a gang for this Faction. Gangs will earn you money and faction reputation";
}
} else {
const checkResult = Player.canAccessGang();
data.enabled = checkResult.success;
data.title = "Create Gang";
data.tooltip = !checkResult.success ? checkResult.message : "";
data.description = "Create a gang for this Faction. Gangs will earn you money and faction reputation";
}
const manageGang = (): void => {
// If player already has a gang, just go to the gang UI
if (Player.inGang()) {
return Router.toPage(Page.Gang);
}
setGangOpen(true);
};
return (
<>
<Box>
<Paper sx={{ my: 1, p: 1 }}>
<Tooltip title={data.tooltip}>
<span>
<Button onClick={manageGang} disabled={!data.enabled}>
{data.title}
</Button>
</span>
</Tooltip>
<Typography>{data.description}</Typography>
</Paper>
</Box>
<CreateGangModal facName={factionName} open={gangOpen} onClose={() => setGangOpen(false)} />
</>
);
}
+1 -1
View File
@@ -23,7 +23,7 @@ export const GangConstants = {
FactionName.SpeakersForTheDead,
FactionName.NiteSec,
FactionName.TheBlackHand,
] as string[],
] as FactionName[],
GangKarmaRequirement: -54000,
/** Normal number of game cycles processed at once (2 seconds) */
minCyclesToProcess: 2000 / CONSTANTS.MilliPerCycle,
+26
View File
@@ -0,0 +1,26 @@
import { Result } from "@nsdefs";
import { Player } from "@player";
import { FactionName } from "../Enums";
import { GangConstants } from "./data/Constants";
export function canCreateGang(faction: FactionName): Result {
if (Player.gang) {
return { success: false, message: "You already have a gang." };
}
const checkResult = Player.canAccessGang();
if (!checkResult.success) {
return { success: false, message: checkResult.message };
}
if (!GangConstants.Names.includes(faction)) {
return {
success: false,
message: `${faction} does not allow creating a gang. You can only do that with ${GangConstants.Names.join(
", ",
)}.`,
};
}
if (!Player.factions.includes(faction)) {
return { success: false, message: `You are not a member of ${faction}.` };
}
return { success: true };
}
+4
View File
@@ -14,15 +14,19 @@ export function getArrow(event: KeyboardLikeEvent): Arrow | undefined {
switch (event.key) {
case KEY.UP_ARROW:
case KEY.W:
case KEY.K:
return upArrowSymbol;
case KEY.LEFT_ARROW:
case KEY.A:
case KEY.H:
return leftArrowSymbol;
case KEY.DOWN_ARROW:
case KEY.S:
case KEY.J:
return downArrowSymbol;
case KEY.RIGHT_ARROW:
case KEY.D:
case KEY.L:
return rightArrowSymbol;
}
}
+2 -17
View File
@@ -7,13 +7,13 @@ import { type InternalAPI, type NetscriptContext, setRemovedFunctions } from "..
import { GangPromise, RecruitmentResult } from "../Gang/Gang";
import { Player } from "@player";
import { FactionName } from "@enums";
import { GangConstants } from "../Gang/data/Constants";
import { AllGangs } from "../Gang/AllGangs";
import { GangMemberTasks } from "../Gang/GangMemberTasks";
import { GangMemberUpgrades } from "../Gang/GangMemberUpgrades";
import { helpers } from "../Netscript/NetscriptHelpers";
import { getEnumHelper } from "../utils/EnumHelper";
import { CONSTANTS } from "../Constants";
import { canCreateGang } from "../Gang/helpers";
export function NetscriptGang(): InternalAPI<IGang> {
/** Functions as an API check and also returns the gang object */
@@ -40,26 +40,11 @@ export function NetscriptGang(): InternalAPI<IGang> {
const gangFunctions: InternalAPI<IGang> = {
createGang: (ctx) => (_faction) => {
const faction = getEnumHelper("FactionName").nsGetMember(ctx, _faction);
if (Player.gang) {
return false;
}
const checkResult = Player.canAccessGang();
const checkResult = canCreateGang(faction);
if (!checkResult.success) {
helpers.log(ctx, () => checkResult.message);
return false;
}
if (!GangConstants.Names.includes(faction)) {
helpers.log(
ctx,
() =>
`${faction} does not allow creating a gang. You can only do that with ${GangConstants.Names.join(", ")}.`,
);
return false;
}
if (!Player.factions.includes(faction)) {
helpers.log(ctx, () => `You are not a member of ${faction}.`);
return false;
}
const isHacking = faction === FactionName.NiteSec || faction === FactionName.TheBlackHand;
Player.startGang(faction, isHacking);
-1
View File
@@ -89,7 +89,6 @@ export function NetscriptGrafting(): InternalAPI<IGrafting> {
Player.startFocusing();
Router.toPage(Page.Work);
} else if (wasFocusing) {
Player.stopFocusing();
Router.toPage(Page.Terminal);
}
-9
View File
@@ -283,7 +283,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.startFocusing();
Router.toPage(Page.Work);
} else if (wasFocusing) {
Player.stopFocusing();
Router.toPage(Page.Terminal);
}
helpers.log(ctx, () => `Started ${classType} at ${universityName}`);
@@ -365,7 +364,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.startFocusing();
Router.toPage(Page.Work);
} else if (wasFocusing) {
Player.stopFocusing();
Router.toPage(Page.Terminal);
}
helpers.log(ctx, () => `Started training ${classType} at ${gymName}`);
@@ -570,7 +568,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Router.toPage(Page.Work);
return true;
} else if (Player.focus && !focus) {
Player.stopFocusing();
Router.toPage(Page.Terminal);
return true;
}
@@ -709,7 +706,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.startFocusing();
Router.toPage(Page.Work);
} else if (wasFocused) {
Player.stopFocusing();
Router.toPage(Page.Terminal);
}
helpers.log(ctx, () => `Began working at '${companyName}' with position '${jobName}'`);
@@ -834,7 +830,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.startFocusing();
Router.toPage(Page.Work);
} else if (wasFocusing) {
Player.stopFocusing();
Router.toPage(Page.Terminal);
}
helpers.log(ctx, () => `Started carrying out hacking contracts for '${faction.name}'`);
@@ -855,7 +850,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.startFocusing();
Router.toPage(Page.Work);
} else if (wasFocusing) {
Player.stopFocusing();
Router.toPage(Page.Terminal);
}
helpers.log(ctx, () => `Started carrying out field missions for '${faction.name}'`);
@@ -876,7 +870,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.startFocusing();
Router.toPage(Page.Work);
} else if (wasFocusing) {
Player.stopFocusing();
Router.toPage(Page.Terminal);
}
helpers.log(ctx, () => `Started carrying out security work for '${faction.name}'`);
@@ -1009,7 +1002,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.startFocusing();
Router.toPage(Page.Work);
} else if (wasFocusing) {
Player.stopFocusing();
Router.toPage(Page.Terminal);
}
helpers.log(ctx, () => `Began creating program: '${programName}'`);
@@ -1059,7 +1051,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.startFocusing();
Router.toPage(Page.Work);
} else if (wasFocusing) {
Player.stopFocusing();
Router.toPage(Page.Terminal);
}
return crimeTime;
+9 -18
View File
@@ -26,21 +26,21 @@ import { getEnumHelper } from "../utils/EnumHelper";
import { CONSTANTS } from "../Constants";
import { getDarknetVolatilityMult } from "../DarkNet/effects/effects";
export function NetscriptStockMarket(): InternalAPI<StockAPI> {
/** Checks if the player has TIX API access. Throws an error if the player does not */
const checkTixApiAccess = function (ctx: NetscriptContext): void {
if (!Player.hasTixApiAccess) {
throw helpers.errorMessage(ctx, `You don't have TIX API Access! Cannot use ${ctx.function}()`);
}
};
const getStockFromSymbol = function (ctx: NetscriptContext, symbol: string): Stock {
export const getStockFromSymbol = function (ctx: NetscriptContext, symbol: string): Stock {
const stock = SymbolToStockMap[symbol];
if (stock == null) {
throw helpers.errorMessage(ctx, `Invalid stock symbol: '${symbol}'`);
}
return stock;
};
export function NetscriptStockMarket(): InternalAPI<StockAPI> {
/** Checks if the player has TIX API access. Throws an error if the player does not */
const checkTixApiAccess = function (ctx: NetscriptContext): void {
if (!Player.hasTixApiAccess) {
throw helpers.errorMessage(ctx, `You don't have TIX API Access! Cannot use ${ctx.function}()`);
}
};
const stockFunctions: InternalAPI<StockAPI> = {
@@ -353,12 +353,3 @@ export function NetscriptStockMarket(): InternalAPI<StockAPI> {
return stockFunctions;
}
export const getStockFromSymbol = function (ctx: NetscriptContext, symbol: string): Stock {
const stock = SymbolToStockMap[symbol];
if (stock == null) {
throw helpers.errorMessage(ctx, `Invalid stock symbol: '${symbol}'`);
}
return stock;
};
+1 -1
View File
@@ -25,7 +25,7 @@ export const root = "" as Directory;
* #: Invalid because it might have a use in the terminal in the future.
* (quote marks): Invalid to avoid conflict with quote marks used in the terminal.
* (whitespace): Invalid to avoid confusion with terminal command separator */
const invalidCharacters = ["/", "*", "?", "[", "]", "!", "\\", "~", "|", "#", '"', "'"];
export const invalidCharacters = ["/", "*", "?", "[", "]", "!", "\\", "~", "|", "#", '"', "'"] as const;
/** A valid character is any character that is not one of the invalid characters */
export const oneValidCharacter = `[^${escapeRegExp(invalidCharacters.join(""))}\\s]`;
+3 -1
View File
@@ -5,9 +5,11 @@ import { FilePath, resolveFilePath } from "./FilePath";
type WithTextExtension = string & { __fileType: "Text" };
export type TextFilePath = FilePath & WithTextExtension;
export const validTextExtensions = [".txt", ".json", ".css"];
/** Check extension only */
export function hasTextExtension(path: string): path is WithTextExtension {
return path.endsWith(".txt") || path.endsWith(".json") || path.endsWith(".css");
return validTextExtensions.some((extension) => path.endsWith(extension));
}
/** Sanitize a player input, resolve any relative paths, and for imports add the correct extension if missing */
+6
View File
@@ -212,6 +212,12 @@ export class PlayerObject extends Person implements IPlayer {
delete player.jobs[loadedCompanyName as CompanyName];
}
}
// A bug created ill-formed UTF-16 darknet hostnames that caused the in-game editor to crash. Player.currentServer
// may point to one of these invalid hostnames. This code migrates the invalid hostnames and protects against
// similar issues in the future.
if (!player.currentServer.isWellFormed()) {
player.currentServer = player.currentServer.toWellFormed();
}
return player;
}
}
@@ -54,8 +54,9 @@ export function hasGangWith(this: PlayerObject, facName: FactionName): boolean {
}
export function startGang(this: PlayerObject, factionName: FactionName, hacking: boolean): void {
// isFactionWork handles null internally, finishWork might need to be run with true
if (isFactionWork(this.currentWork) && this.currentWork.factionName === factionName) this.finishWork(false);
if (isFactionWork(this.currentWork) && this.currentWork.factionName === factionName) {
this.finishWork(true);
}
this.gang = new Gang(factionName, hacking);
+1 -1
View File
@@ -2835,7 +2835,7 @@ export interface Singularity {
* @returns - a list of programs available for purchase on the dark web, or [] if Tor has not
* been purchased
*/
getDarkwebPrograms(): string[];
getDarkwebPrograms(): ProgramName[];
/**
* Check the price of an exploit on the dark web
+19
View File
@@ -148,6 +148,25 @@ export function loadAllServers(saveString: string): void {
if (!(server instanceof Server) && !(server instanceof HacknetServer) && !(server instanceof DarknetServer)) {
throw new Error(`Server ${serverName} is not an instance of Server or HacknetServer or DarknetServer.`);
}
// Sanitize hostname
// A bug created ill-formed UTF-16 darknet hostnames that caused the in-game editor to crash. This code migrates
// those invalid hostnames and protects against similar issues in the future.
if (!server.hostname.isWellFormed()) {
server.hostname = server.hostname.toWellFormed();
for (const script of server.scripts.values()) {
script.server = server.hostname;
}
if (server.savedScripts) {
for (const script of server.savedScripts) {
script.server = server.hostname;
}
}
}
// Sanitize hostnames in server.serversOnNetwork
for (const [index, value] of server.serversOnNetwork.entries()) {
server.serversOnNetwork[index] = value.toWellFormed();
}
AllServers.set(server.hostname, server);
AllServers.set(server.ip, server);
}
+13
View File
@@ -14,6 +14,7 @@ export const TerminalHelpText: string[] = [
" connect [hostname] Connects to a remote server",
" cp [src] [dest] Copy a file",
" download [script/text file] Downloads scripts or text files to your computer",
" upload [dir] Upload scripts or text files from your computer",
" expr [math expression] Evaluate a mathematical expression",
" free Check the machine's memory (RAM) usage",
" grep [opts]... pattern [file]... Search for PATTERN (string/regular expression) in each FILE and print results to terminal",
@@ -216,6 +217,18 @@ export const HelpTexts: Record<string, string[]> = {
"Download all text files: download *.txt",
" ",
],
upload: [
"Usage: upload [dir]",
" ",
"Uploads a directory from your computer into the game.",
" ",
"Examples:",
" ",
" upload path/to/dir",
" ",
" upload .",
" ",
],
expr: [
"Usage: expr [mathematical expression]",
" ",
+2
View File
@@ -44,6 +44,7 @@ import { check } from "./commands/check";
import { connect } from "./commands/connect";
import { cp } from "./commands/cp";
import { download } from "./commands/download";
import { upload } from "./commands/upload";
import { expr } from "./commands/expr";
import { free } from "./commands/free";
import { grep } from "./commands/grep";
@@ -105,6 +106,7 @@ export const TerminalCommands: Record<string, (args: (string | number | boolean)
connect: connect,
cp: cp,
download: download,
upload: upload,
expr: expr,
free: free,
grep: grep,
+151
View File
@@ -0,0 +1,151 @@
import { Terminal } from "../../Terminal";
import type { BaseServer } from "../../Server/BaseServer";
import { combinePath, isFilePath } from "../../Paths/FilePath";
import { hasTextExtension, validTextExtensions } from "../../Paths/TextFilePath";
import { hasScriptExtension, validScriptExtensions } from "../../Paths/ScriptFilePath";
import { PromptEvent } from "../../ui/React/PromptManager";
import type { ContentFilePath } from "../../Paths/ContentFile";
import { invalidCharacters } from "../../Paths/Directory";
import { pluralize } from "../../utils/I18nUtils";
function pickDirectory(): Promise<FileList | null> {
return new Promise((resolve) => {
const input = document.createElement("input");
input.type = "file";
input.webkitdirectory = true;
input.onchange = () => {
resolve(input.files);
};
input.oncancel = () => {
resolve(null);
};
input.click();
});
}
function askConfirm(txt: string): Promise<boolean> {
return new Promise((resolve, reject) => {
PromptEvent.emit({
txt,
resolve: (value: string | boolean) => {
if (typeof value === "string") {
reject(new Error("PromptEvent got a string, expected boolean"));
} else {
resolve(value);
}
},
});
});
}
async function uploadAsync(args: (string | number | boolean)[], server: BaseServer) {
if (args.length !== 1) {
return Terminal.error("Incorrect usage of upload command. Usage: upload [dir]");
}
const destinationInput = String(args[0]);
const destination = Terminal.getDirectory(destinationInput);
if (destination === null) {
return Terminal.error(`Could not resolve ${destinationInput} as a Directory`);
}
const files = await pickDirectory();
if (files === null || files.length === 0) {
return;
}
const withPath: (
| { badPath: string }
| { overwrite: ContentFilePath; file: File }
| { create: ContentFilePath; file: File }
)[] = [...files].map((f) => {
const { webkitRelativePath } = f;
/*
If the player has a directory /home/alice/foo/bar on her computer
and wants to upload the contents of the directory
and if the directory hierarchy looks like this:
/home/alice/foo/bar
├── hello
│ └── world.js
└── more
└── files.txt
`webkitRelativePath` for world.js will be "bar/hello/world.js"
`path` will be "hello/world.js"
`webkitRelativePath` for files.txt will "bar/more/files.txt"
`path` will be "more/files.txt"
*/
const path = webkitRelativePath.substring(1 + webkitRelativePath.indexOf("/"));
if (!isFilePath(path)) {
return { badPath: path };
}
const destFilePath = combinePath(destination, path);
let fileExists: boolean;
if (hasTextExtension(destFilePath)) {
fileExists = server.textFiles.has(destFilePath);
} else if (hasScriptExtension(destFilePath)) {
fileExists = server.scripts.has(destFilePath);
} else {
return { badPath: path };
}
if (fileExists) {
return {
overwrite: destFilePath,
file: f,
};
}
return {
create: destFilePath,
file: f,
};
});
const overwrite = withPath.filter((item) => "overwrite" in item);
const skipped = withPath.filter((item) => "badPath" in item);
const create = withPath.filter((item) => "create" in item);
const destForPrint = destination === "" ? "/" : destination;
const lines = [`Upload files to ${destForPrint}?`];
if (overwrite.length !== 0) {
lines.push(
"",
`${pluralize(overwrite.length, "file")} will be overwritten:`,
...overwrite.map(({ overwrite }) => overwrite),
);
}
if (skipped.length !== 0) {
const extensions = [...validScriptExtensions, ...validTextExtensions];
lines.push(
"",
`Characters ${invalidCharacters
.filter((v) => v !== "/")
.join(" ")} and whitespace are not allowed in file paths.`,
`Only file extensions ${extensions.join(", ")} are allowed.`,
"A file name must have at least one character before the extension.",
"",
`${pluralize(skipped.length, "file")} will be skipped due to prohibited file paths:`,
...skipped.map(({ badPath }) => badPath),
);
}
if (create.length !== 0) {
lines.push("", `${pluralize(create.length, "new file")} will be created:`, ...create.map(({ create }) => create));
}
if (!(await askConfirm(lines.join("\n")))) {
return;
}
Terminal.print(`Starting to upload files to ${destForPrint}`);
for (const item of [...overwrite, ...create]) {
const destFilePath = "create" in item ? item.create : item.overwrite;
let text: string | undefined = undefined;
try {
text = await item.file.text();
} catch (error) {
console.error(error);
Terminal.error(`Failed to upload ${destFilePath}. Error: ${error}`);
continue;
}
server.writeToContentFile(destFilePath, text);
}
Terminal.print(`Successfully uploaded files to ${destForPrint}`);
}
export function upload(args: (string | number | boolean)[], server: BaseServer): void {
uploadAsync(args, server).catch((error) => {
console.error(error);
Terminal.error(`Error while uploading files. Error: ${error}`);
});
}
@@ -185,6 +185,7 @@ export async function getTabCompletionPossibilities(terminalText: string, baseDi
case "cd":
case "ls":
case "upload":
if (onFirstCommandArg && !relativeDir) addDirectories();
return possibilities;
+1 -1
View File
@@ -81,7 +81,7 @@ export function extendAcornWalkForTypeScriptNodes(base: any) {
}
// Only walk relevant TypeScript nodes.
base.TSModuleBlock = base.BlockStatement;
base.TSAsExpression = base.TSNonNullExpression = base.ExpressionStatement;
base.TSTypeAssertion = base.TSAsExpression = base.TSNonNullExpression = base.ExpressionStatement;
base.TSModuleDeclaration = (node: any, state: any, callback: any) => {
callback(node.body, state);
};
+6 -2
View File
@@ -533,14 +533,18 @@ export function CharacterStats(): React.ReactElement {
effValue: Player.mults.crime_money * currentNodeMults.CrimeMoney,
color: Settings.theme.money,
},
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
{
mult: "Darknet Money",
value: Player.mults.dnet_money,
effValue: Player.mults.dnet_money * currentNodeMults.DarknetMoneyMultiplier,
color: Settings.theme.money,
},
]}
color={Settings.theme.combat}
color={Settings.theme.money}
/>
{Player.canAccessBladeburner() && currentNodeMults.BladeburnerRank > 0 && (
<MultiplierTable
+7
View File
@@ -266,6 +266,13 @@ export function GameRoot(): React.ReactElement {
}
break;
}
// If the current page is Page.Work, the player is focusing on their current work. Switching to another page ends
// that focus, so we must call Player.stopFocusing() immediately after Router.toPage() to keep Player.focus in
// sync. Instead of repeating this logic wherever Router.toPage() is called, we should centralize the check and
// the Player.stopFocusing() call here.
if (pageWithContext.page === Page.Work && page !== Page.Work && Player.currentWork && Player.focus) {
Player.stopFocusing();
}
setNextPage({ page, ...context } as PageWithContext);
},
back: () => {
@@ -563,8 +563,8 @@ export async function main(ns) {
materials for all NS APIs.
</li>
<li>
The <DocumentationLink page="help/beginner_faq.md">FAQ</DocumentationLink> contains questions often asked
by beginners of the game.
The <DocumentationLink page="help/faq.md">FAQ</DocumentationLink> contains questions often asked by
beginners of the game.
</li>
</ul>
<Typography fontWeight="fontWeightBold">
-6
View File
@@ -219,7 +219,6 @@ export function WorkInProgressRoot(): React.ReactElement {
},
unfocus: () => {
Router.toPage(Page.City);
Player.stopFocusing();
},
},
title: `You are attempting ${crime.workName}`,
@@ -266,7 +265,6 @@ export function WorkInProgressRoot(): React.ReactElement {
},
unfocus: () => {
Router.toPage(Page.Location, { location: Locations[classWork.location] });
Player.stopFocusing();
},
},
title: (
@@ -303,7 +301,6 @@ export function WorkInProgressRoot(): React.ReactElement {
},
unfocus: () => {
Router.toPage(Page.Terminal);
Player.stopFocusing();
},
},
title: (
@@ -334,7 +331,6 @@ export function WorkInProgressRoot(): React.ReactElement {
},
unfocus: () => {
Router.toPage(Page.Terminal);
Player.stopFocusing();
},
},
title: (
@@ -388,7 +384,6 @@ export function WorkInProgressRoot(): React.ReactElement {
},
unfocus: () => {
Router.toPage(Page.Faction, { faction });
Player.stopFocusing();
},
},
title: (
@@ -439,7 +434,6 @@ export function WorkInProgressRoot(): React.ReactElement {
Router.toPage(Page.Job);
},
unfocus: () => {
Player.stopFocusing();
Router.toPage(Page.Job);
},
},
+3 -1
View File
@@ -186,7 +186,9 @@ Copy your save here if possible
\`\`\`
`.trim();
const issueUrl = `${newIssueUrl}?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;
const issueUrl = `${newIssueUrl}?title=${encodeURIComponent(title.toWellFormed())}&body=${encodeURIComponent(
body.toWellFormed(),
)}`;
return {
metadata,
+15
View File
@@ -105,3 +105,18 @@ export function getKeyFromReactElements(a: string | React.JSX.Element, b: string
const keyOfb = typeof b === "string" ? b : b.key ?? "";
return keyOfA + keyOfb;
}
const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
/**
* input.split("") operates on UTF-16 code units and can break surrogate pairs.
* For example, 'a🅱️b' is 'a\uD83C\uDD71\uFE0Fb'. A naive reverse yields 'b\uFE0F\uDD71\uD83Ca', which is ill-formed
* UTF-16 and not 'b🅱️a' as expected.
* Passing such a string to encodeURIComponent may throw a URIError (e.g. in Monaco editor code when processing model
* ids).
*/
export function safelyReverseString(input: string): string {
return Array.from(graphemeSegmenter.segment(input), (s) => s.segment)
.reverse()
.join("");
}
+49 -1
View File
@@ -61,9 +61,11 @@ import { DarknetServer } from "../../../src/Server/DarknetServer";
import { isDirectoryPath } from "../../../src/Paths/Directory";
import { isFilePath } from "../../../src/Paths/FilePath";
import { LAB_CACHE_NAME } from "../../../src/DarkNet/effects/labyrinth";
import { generateCacheFilename } from "../../../src/DarkNet/effects/cacheFiles";
import { generateCacheFilename, getStockReward } from "../../../src/DarkNet/effects/cacheFiles";
import { getAllDarknetServers } from "../../../src/DarkNet/utils/darknetNetworkUtils";
import { prestigeAugmentation } from "../../../src/Prestige";
import { initStockMarket, StockMarket } from "../../../src/StockMarket/StockMarket";
import { StockSymbol } from "@enums";
beforeAll(() => {
initGameEnvironment();
@@ -778,6 +780,7 @@ describe("mutateDarknet and webstorm", () => {
function validatePath(hostname: string): void {
expectWithMessage(isDirectoryPath(`${hostname}/`), true, `Invalid hostname: ${hostname}`);
expectWithMessage(isFilePath(`${hostname}/data.txt`), true, `Invalid hostname: ${hostname}`);
expectWithMessage(hostname.isWellFormed(), true, `Malformed hostname: ${hostname}`);
}
describe("Darknet server name generator", () => {
@@ -839,3 +842,48 @@ describe("Clue filename generator", () => {
}
});
});
describe("Stock cache reward", () => {
test("stock reward does not exceed maxShares and falls back to money reward", () => {
initStockMarket();
const remaining = 3;
// Fill every stock to near capacity so no matter which one is randomly picked, it triggers clamping
for (const stockName of Object.keys(StockSymbol)) {
const stock = StockMarket[stockName];
stock.playerShares = stock.maxShares - remaining;
stock.playerShortShares = 0;
}
// Use high difficulty to ensure the unclamped share count would exceed remaining
const difficulty = 100;
const result = getStockReward(difficulty);
// Should have awarded at most `remaining` shares
expect(result).toContain(`${remaining} shares`);
// Verify the chosen stock was clamped to exactly maxShares
for (const stockName of Object.keys(StockSymbol)) {
const stock = StockMarket[stockName];
expect(stock.playerShares).toBeLessThanOrEqual(stock.maxShares);
}
});
test("stock reward falls back to money when stock is fully owned", () => {
initStockMarket();
// Fill every stock to max capacity
for (const stockName of Object.keys(StockSymbol)) {
const stock = StockMarket[stockName];
stock.playerShares = stock.maxShares;
stock.playerShortShares = 0;
}
const moneyBefore = Player.money;
const result = getStockReward(5);
// Should have fallen back to a money reward
expect(result).toContain("discovered a cache with");
expect(Player.money).toBeGreaterThan(moneyBefore);
});
});
+22
View File
@@ -6,6 +6,7 @@ import * as db from "../../../src/db";
import * as FileUtils from "../../../src/utils/FileUtils";
import type { SaveData } from "../../../src/types";
import { calculateExp } from "../../../src/PersonObjects/formulas/skill";
import { GetAllServers, GetServer } from "../../../src/Server/AllServers";
async function loadGameFromSaveData(saveData: SaveData) {
// Simulate loading the data in IndexedDB
@@ -132,4 +133,25 @@ describe("v3", () => {
expect(mockedDownload).not.toHaveBeenCalled();
});
});
test("Malformed hostname", async () => {
const saveData = new Uint8Array(fs.readFileSync("test/jest/Migration/save-files/malformed-hostname.gz"));
await loadGameFromSaveData(saveData);
for (const server of GetAllServers(true)) {
expect(server.hostname.isWellFormed()).toBe(true);
for (const script of server.scripts.values()) {
expect(script.server).toStrictEqual(server.hostname);
}
if (server.savedScripts) {
for (const script of server.savedScripts) {
expect(script.server).toStrictEqual(server.hostname);
}
}
for (const hostname of server.serversOnNetwork) {
expect(hostname.isWellFormed()).toBe(true);
expect(GetServer(hostname)).not.toBeNull();
}
}
expect(() => Player.getCurrentServer()).not.toThrow();
});
});
Binary file not shown.
+64 -6
View File
@@ -1,8 +1,10 @@
import { FactionName } from "@enums";
import { Player } from "@player";
import { Gang } from "../../../src/Gang/Gang";
import { AllGangs } from "../../../src/Gang/AllGangs";
import { getNS, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities";
import { getNS, getWorkerScriptAndNS, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities";
import { GangConstants } from "../../../src/Gang/data/Constants";
import { joinFaction } from "../../../src/Faction/FactionHelpers";
import { Factions } from "../../../src/Faction/Factions";
beforeAll(() => {
initGameEnvironment();
@@ -10,12 +12,16 @@ beforeAll(() => {
beforeEach(() => {
setupBasicTestingEnvironment();
// Give the player a gang so gang API is accessible
Player.gang = new Gang(FactionName.SlumSnakes, false);
Player.sourceFiles.set(2, 3);
});
describe("ns.gang.getAllGangInformation", () => {
it("should return territory and power info for all gangs including the player's", () => {
beforeEach(() => {
// Give the player a gang so gang API is accessible
Player.startGang(FactionName.SlumSnakes, false);
});
test("should return territory and power info for all gangs including the player's", () => {
const ns = getNS();
const info = ns.gang.getAllGangInformation();
const gangNames = Object.keys(info);
@@ -35,7 +41,7 @@ describe("ns.gang.getAllGangInformation", () => {
}
});
it("should return copies, not references to the original AllGangs data", () => {
test("should return copies, not references to the original AllGangs data", () => {
const ns = getNS();
const info = ns.gang.getAllGangInformation();
@@ -44,3 +50,55 @@ describe("ns.gang.getAllGangInformation", () => {
expect(AllGangs[FactionName.SlumSnakes].power).not.toBe(999999);
});
});
describe("createGang", () => {
test("Success", () => {
const ns = getNS();
Player.karma = GangConstants.GangKarmaRequirement;
joinFaction(Factions[FactionName.SlumSnakes]);
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(true);
});
describe("Failure", () => {
test("Already have a gang", () => {
const { ws, ns } = getWorkerScriptAndNS();
Player.karma = GangConstants.GangKarmaRequirement;
joinFaction(Factions[FactionName.SlumSnakes]);
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(true);
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(false);
expect(ws.scriptRef.logs[0]).toMatch("You already have a gang");
expect(ns.gang.createGang(FactionName.Tetrads)).toBe(false);
expect(ws.scriptRef.logs[1]).toMatch("You already have a gang");
});
test("Disabled by advanced options", () => {
const { ws, ns } = getWorkerScriptAndNS();
Player.bitNodeOptions.disableGang = true;
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(false);
expect(ws.scriptRef.logs[0]).toMatch("Gang is disabled by advanced options");
});
test("Not have Source-File 2", () => {
const { ws, ns } = getWorkerScriptAndNS();
Player.sourceFiles.set(2, 0);
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(false);
expect(ws.scriptRef.logs[0]).toMatch("You do not have Source-File 2");
});
test("Not enough karma", () => {
const { ws, ns } = getWorkerScriptAndNS();
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(false);
expect(ws.scriptRef.logs[0]).toMatch("Your karma must be less than or equal to");
});
test("Invalid gang faction", () => {
const { ws, ns } = getWorkerScriptAndNS();
Player.karma = GangConstants.GangKarmaRequirement;
joinFaction(Factions[FactionName.SlumSnakes]);
expect(ns.gang.createGang(FactionName.Illuminati)).toBe(false);
expect(ws.scriptRef.logs[0]).toMatch("does not allow creating a gang");
});
test("Not a faction member", () => {
const { ws, ns } = getWorkerScriptAndNS();
Player.karma = GangConstants.GangKarmaRequirement;
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(false);
expect(ws.scriptRef.logs[0]).toMatch("You are not a member of");
});
});
});
+2 -2
View File
@@ -178,9 +178,9 @@ describe("getTabCompletionPossibilities", function () {
}
});
it("completes the ls and cd commands", async () => {
it("completes the ls, cd and upload commands", async () => {
writeFiles();
for (const command of ["ls", "cd"]) {
for (const command of ["ls", "cd", "upload"]) {
const options = await getTabCompletionPossibilities(`${command} `, root);
expect(options.sort()).toEqual(["folder1/", "anotherFolder/"].sort());
}