Compare commits

..

115 Commits

Author SHA1 Message Date
Snarling
d2c7b2504c Release 2.6.1 (#1300) 2024-05-22 01:14:52 -04:00
catloversg
175af0bd28 BUGFIX: Improve implementation of getRandomInt (#1282) 2024-05-18 15:12:06 -07:00
TheAimMan
8deb907b89 BUGFIX: Correct BN10 Sleeve starting Shock (#1285) 2024-05-17 13:31:40 -07:00
gmcew
36e3dd73ac IPVGO: Tetrads do def (#1284) 2024-05-17 04:59:21 -07:00
catloversg
585e089976 DOCUMENTATION: Clarify 2 forms of flag in ns.flags (#1283) 2024-05-17 04:57:10 -07:00
Vilsol
7a4a973c06 DOC: remove redundant @remarks for sqlinject() function (#1281) 2024-05-17 04:55:50 -07:00
gmcew
1d8a1d5089 UI: Trigger RAM checking on tab closure (#1279)
Fixes UI bug described at https://discord.com/channels/415207508303544321/415213413745164318/1240361787564490854
2024-05-17 04:46:04 -07:00
gmcew
7113ee5425 UI: Hacknet terminology consistency (#1256)
* UI labels/Augment effects reworded to remove the "Node" parts
* No changes to API-facing labels/savedata/functions
2024-05-17 04:44:09 -07:00
David Walker
38d99ff15e IPVGO: Remove unneeded functions from boardState.ts (#1270) 2024-05-16 12:26:18 -07:00
Snarling
b7962ad8ab Increment version number early
This is needed to avoid API break spam every time the player loads the game.
2024-05-14 20:18:00 -04:00
Snarling
ed308b4fa6 Changelog update 2024-05-14 20:12:55 -04:00
Snarling
574c284321 API: Add API break utilities, and add an API break for bladeburner.getCurrentAction (#1248) 2024-05-14 19:24:03 -04:00
catloversg
9dc3b22919 BUGFIX: Tail window position does not update when being dragged (#1275) 2024-05-13 00:27:21 -07:00
catloversg
25afecc0ec CORPORATION: Rename functions in Actions.ts (#1272)
This should be a straight rename, no functionality changes.
2024-05-12 14:52:07 -07:00
catloversg
7f5bc5700e CORPORATION: Refactor bribery (#1268)
This also removes the useless restriction Player.hasGangWith(factionName). When the corporation is strong enough to bribe, the gang is useless. This problem was discussed on Discord.
2024-05-12 14:49:11 -07:00
Michael Taylor
da6262c856 Modify the source code TS file mode to be a regular (non-executable) file (#1269) 2024-05-11 21:57:21 -07:00
catloversg
1288d1c289 API: Update getInfiltration API (#1235) 2024-05-11 20:13:59 -04:00
catloversg
e478b9a224 MISC: Log invalid file request in Electron app (#1267) 2024-05-11 17:04:54 -07:00
catloversg
7ee7a79763 INFILTRATION: Fix a crash when rendering InfiltrationRoot with an invalid location (#1266) 2024-05-11 17:04:02 -07:00
gmcew
519b4fef44 MISC: Hamming Code Contract description clarification (#1244)
Rewording of the Hamming code contract wording based on suggestion at discord.
There's further scope on other contracts to clean up inconsistencies in example formatting.
2024-05-11 17:00:36 -07:00
Snarling
e23db93c8b GO: Alternate fix for race conditions (#1260) 2024-05-11 19:58:59 -04:00
gmcew
1b8205e9d5 UI: Bladeburner console to use Settings.TimestampsFormat for logging (#1265) 2024-05-11 19:10:55 -04:00
catloversg
52111f6e07 INFILTRATION: Format damage in Intro UI (#1264) 2024-05-10 18:56:24 -07:00
catloversg
8ebfcdb089 MISC: Fix bugs in useRerender hook and ns.moveTail (#1263) 2024-05-10 18:55:45 -07:00
catloversg
19984a6f22 UI: Fix clipped numbers when values are too big in Augmentations page (#1250) 2024-05-10 18:51:12 -07:00
catloversg
b88361921e MISC: Remove tsdoc-metadata.json (#1262) 2024-05-10 18:50:27 -07:00
Michael Ficocelli
591ad45154 IPVGO: Handle RNG seeding and visual board updating bugs (#1258) 2024-05-10 01:58:09 -07:00
Michael Ficocelli
b53c35126e IPVGO: Provide API for getting game stats per opponent (#1255)
Give users access to wins, losses, stat bonuses, and favor gained
2024-05-10 01:57:03 -07:00
catloversg
35c32e2871 BUGFIX: Fix unresolved promise in ns.prompt API (#1257) 2024-05-10 01:55:50 -07:00
catloversg
e55387df4d INFILTRATION: Rename variables in slash game (#1253)
This PR renames variables in the slash game to match the new description/wording in #1243.
2024-05-10 01:39:20 -07:00
catloversg
2414949c2c MISC: Update formatHashes function (#1252)
When hash/hashRate value is too small, formatHashes converts it to the useless string 0.000. This PR fixes that.
2024-05-10 01:38:38 -07:00
catloversg
309cd55085 UI: Change buttons in Tutorial (#1251) 2024-05-10 01:34:45 -07:00
catloversg
8289b23cff UI: Wrap long text in Active Scripts UI (#1247) 2024-05-09 17:07:41 -07:00
catloversg
aef362204d MISC: Handle error when getting save data (#1241) 2024-05-09 02:19:30 -07:00
TheAimMan
bfb9841832 DARKWEB: Update DarkWeb.tsx to buy all possible programs using buy -a (#1240) 2024-05-09 02:18:03 -07:00
catloversg
6f009679ad INFILTRATION: Add HP and damage to Intro UI (#1242) 2024-05-08 17:10:20 -07:00
catloversg
6a1691fe54 CORPORATION: Update tooltip of storage space (#1237)
The tooltip of the storage space only shows sizes of materials/products. This is confusing for newbies. They use "Unit" (number of material/product units) when buying materials, but that tooltip only shows sizes without any description.
2024-05-08 16:29:03 -07:00
catloversg
bc71b8e18f INFILTRATION: Update slash game wording / balance (#1243) 2024-05-06 14:07:27 -04:00
catloversg
a79d7f9e45 BUGFIX: Fix wrong zoom factor in Electron app (#1245) 2024-05-06 13:13:56 -04:00
catloversg
478646290e BUGFIX: Fix #795 (#1231) 2024-05-02 18:39:30 -04:00
catloversg
5696db2788 MISC: Fix ghost processes when Electron code crashes too early (#1238) 2024-05-02 18:38:39 -04:00
catloversg
d4bdb8de2b MISC: Rework reputation bonus after installing a backdoor (#1236) 2024-04-28 18:35:56 -07:00
catloversg
4d3dbf169d INFILTRATION: Increase timePreparing in SlashGame if players have WKSharmonizer (#1232) 2024-04-27 02:13:16 -07:00
catloversg
dc4a85e591 CORPORATION: Fix wrong error message in buyMaterial API (#1234) 2024-04-27 02:10:58 -07:00
catloversg
de8883ed0f BUGFIX: Fix #998 (#1233)
CorruptableText wasn't updating state properly
2024-04-27 02:10:17 -07:00
catloversg
3fac471d51 EDITOR: Add "arguments" to the keyword list (#1230) 2024-04-24 16:10:20 -04:00
Michael Ficocelli
0f23c95737 IPVGO: Remove opponent from react state (#1222) 2024-04-24 16:10:01 -04:00
Snarling
f4fcb5cde1 Changelog update 2024-04-23 21:03:58 -04:00
David Walker
e56ed353e5 (Partial) fix for #795 (#1223) 2024-04-23 20:40:59 -04:00
hydroflame
d3b9f32c3f MISC: Rename an aug (#1218) 2024-04-23 20:40:12 -04:00
David Walker
384d1c1a2b NETSCRIPT: A minorly breaking change around script launch, and refactoring. (#1213) 2024-04-23 20:21:05 -04:00
catloversg
7a1fce6f64 CORPORATION: Fix wrong average price of material (#1227) 2024-04-23 20:14:22 -04:00
catloversg
eba86e4bf0 BUGFIX: Fix #997 (#1226) 2024-04-23 20:05:24 -04:00
Caldwell
a3f9a5c21e update and fix (#1225)
Co-authored-by: Caldwell <15591472+Caldwell-74@users.noreply.github.com>
2024-04-23 20:00:28 -04:00
catloversg
703e7c52ae MISC: Remove unused constants (#1229) 2024-04-23 19:58:45 -04:00
catloversg
7b993f3550 MISC: Remove file-saver (#1217)
Also refactor to dedup our own download code
2024-04-19 13:38:44 -07:00
catloversg
216500ed32 CORPORATION: Add a new API to sell a division (#1210)
Also refactoring around use of "player" variable (whether it is capitalized or not).
2024-04-15 21:19:47 -07:00
Michael Ficocelli
dd3975ab1d IPVGO: Fix formatting to avoid breaking external editors (#1215) 2024-04-11 14:28:06 -07:00
Michael Ficocelli
ed59f325ef IPVGO: Ensure full name of method is recorded in the possibleLogs list, to be matched later (#1207) 2024-04-09 18:24:57 -07:00
Michael Ficocelli
057ccc2a2b IPVGO: Improve error logging to include stacktraces (#1212) 2024-04-09 06:36:05 -07:00
catloversg
1ad6f9f310 CORPORATION: Update documentation (#1209)
New optimal strategy based on API tweaks.
2024-04-08 14:46:16 -07:00
Michael Ficocelli
104a97d711 DOCS: Link to stable instead of dev for player-facing docs (#1208)
* DOCS: Link to stable instead of dev for player-facing docs, to avoid confusion about features that are not yet released or finalized
2024-04-08 14:40:53 -07:00
Snarling
be437c83f6 BLADEBURNER: Allow unsafe positive integers for skill upgrade count (#1211) 2024-04-08 06:33:45 -04:00
catloversg
7ae309edda UI: Increase margin bottom for BitNode Multipliers in Stats page (BN1) (#1204) 2024-04-02 03:07:49 -07:00
p0n24
7ab4ad8174 fix documentation typo (#1203) 2024-03-31 13:53:51 -07:00
catloversg
524714601e CORPORATION: Add missing checks for Export API (#1202) 2024-03-31 13:53:02 -07:00
T.J. Eckman
da7f01cca9 DOCS: Fixed a typo in tutorial (#1201) 2024-03-31 13:51:48 -07:00
Michael Ficocelli
6c9555ba32 IPVGO: Tweak cheat success scaling so it is applicable even to endgame stats (#1194) 2024-03-30 16:22:53 -07:00
Snarling
6beb6e9f95 BLADEBURNER: Followup for #1154 (#1200) 2024-03-29 20:43:28 -04:00
catloversg
61274310d6 DEVMENU: Remove SaveFileDev (#1196)
It was broken and no one could be found who had used it.
2024-03-29 14:01:21 -07:00
Snarling
12f9a2b24d Bugfix
Fix Skill.maxLvl defaulting to 1
2024-03-29 14:27:36 -04:00
catloversg
c467daaf86 DOCUMENTATION: Fix wrong identification of inline mathematical expressions (#1195)
Applies backslashes to all relevant "$".
2024-03-29 01:42:18 -07:00
Snarling
a923041382 Changelog update 2024-03-29 01:20:49 -04:00
Paco Delozanne
ae1ca8f9a6 NETSCRIPT: Add Singularity.getFactionEnemies() (#1192) 2024-03-29 01:16:54 -04:00
Snarling
6669c4da6a BLADEBURNER: Typesafety / refactoring (#1154) 2024-03-28 21:52:37 -04:00
adeilt
5f1a94a9d3 DEV: improve "git blame" for contributors (#1092) 2024-03-28 21:28:05 -04:00
Michael Ficocelli
fe87f1f628 IPVGO: Bugfixes (#1193)
* IPVGO: Explicitly link the generated API documentation in the algorithm design doc
* IPVGO: Fix missing factions in netscript docs
* IPVGO: Linting
* IPVGO: Ensure resetBoardState() logs that a new game has started
2024-03-27 22:02:53 -07:00
catloversg
8553bcb8fc MISC: Support compression of save data (#1162)
* Use Compression Streams API instead of jszip or other libraries.
* Remove usage of base64 in the new binary format.
* Do not convert binary data to string and back. The type of save data is SaveData, it's either string (old base64 format) or Uint8Array (new binary format).
* Proper support for interacting with electron-related code. Electron-related code assumes that save data is in the base64 format.
* Proper support for other tools (DevMenu, pretty-save.js). Full support for DevMenu will be added in a follow-up PR. Check the comments in src\DevMenu\ui\SaveFileDev.tsx for details.
2024-03-27 21:08:09 -07:00
David Walker
75dabd10be NETSCRIPT: add formulas.hacking.growAmount() (#1090)
Also, improve docs.
2024-03-26 03:26:50 -07:00
Michael Ficocelli
d8de22a273 IPVGO: Nerf overly-difficult handicap, [issue #1169] misc bugfixes (#1188)
* IPVGO: Nerf overly-difficult handicap on 5x5 board

* IPVGO: Tweak 5x5 handicap
2024-03-25 16:12:35 -07:00
catloversg
714c1cc9d6 CORPORATION: Update documentation (#1191) 2024-03-25 14:49:43 -07:00
adeilt
08097aaf09 CORPORATION: more granular office size upgrades (#1179)
Allows corporation.upgradeOfficeSize to increase the size of a Corporation office by a non-multiple of 3 and also be charged a corresponding amount of corporate funds. See #1166 for details of current behavior.
2024-03-24 17:37:08 -07:00
catloversg
db226ce0b8 MISC: Remove isString utility function (#1185) 2024-03-23 02:46:52 -07:00
catloversg
c5581e92bc MISC: Make text file (txt, json) clickable when using ls (#1172) 2024-03-23 02:45:48 -07:00
catloversg
7c4cd7ef86 MISC: Update devDependencies (#1186)
Removing unused dependencies
2024-03-22 16:52:34 -07:00
Snarling
28a4af0ddc Changelog update (#1184) 2024-03-21 21:36:18 -04:00
Snarling
0c2a59bb6c CREDITS: Add d0sboots as documented maintainer (#1183) 2024-03-21 18:32:02 -04:00
catloversg
99f7a4cc7b IPvGO: Fix API documentation (#1171)
Proper fix for table pre layout
2024-03-21 14:39:37 -07:00
catloversg
35a34470a2 CORPORATION: update documentation (#1182)
Tweak several formulas
2024-03-21 14:37:50 -07:00
catloversg
c637d0e4e4 SINGULARITY: Fix API documentation (#1181) 2024-03-21 14:27:13 -07:00
Caldwell
803afc5244 SLEEVES: add nextCompletion to SleeveInfiltrationWork (#1177)
* add nextCompletion to InfilWork

and make nextCompletion in BladeburnerWork uniform with other promisePairs
2024-03-20 23:11:12 -07:00
catloversg
bbd942ceca MISC: Suggest automatic translation systems not translate our game (#1180) 2024-03-20 20:39:23 -04:00
Michael Ficocelli
1e5f7184a2 IPVGO: Remove current game history from savefile, re-implement superko (#1175) 2024-03-20 20:37:20 -04:00
catloversg
fc8958af83 MISC: Remove jquery (#1167)
Changes wget, which was the only thing using it.
2024-03-20 14:20:29 -07:00
Michael Ficocelli
6b9f9ef7fa IPVGO: Balance: Reduce base bonuses, increase SF 14.1 to compensate (#1176)
* IPVGO: Balance: Reduce base bonuses, increase SF 14.1 to compensate

* Fix ts doc format

* Do not show "No AI" opponent in stat summary page
2024-03-20 14:12:35 -07:00
Michael Ficocelli
dd4b54406c IPVGO: Bugfix: Require special opponent to be on the fixed size board (#1170) 2024-03-19 14:23:50 -04:00
Michael Ficocelli
d81358c80f IPVGO: Add support to netscript API for game state, current player, and alternate ways to check/wait on AI turn (#1142) 2024-03-19 14:07:15 -04:00
LJ
6aaeb6b59e Fix sing function for bn14 (#1168) 2024-03-18 00:54:20 -04:00
Michael Ficocelli
edf8e24046 IPvGO: Bugfix: ensure the 'No Ai' white player can still pass, and fix missing dead nodes (#1165) 2024-03-17 14:15:53 -07:00
gmcew
c0662599b3 MISC: Stanek UI description corrections (#1161)
Corrected description on charging efficacy against threadcount and time

From https://github.com/bitburner-official/bitburner-src/issues/689, description has been corrected to reflect the scaling for highestCharge and numCharge
2024-03-14 18:51:32 -07:00
catloversg
7ef7b692d0 MISC: Filter servers in autocomplete data (#1164) 2024-03-14 17:55:06 -04:00
FoGsesipod
25ac8432fc BUGFIX: spawn() log reads "seconds" instead of "milliseconds" (#1158) 2024-03-11 15:25:02 -07:00
Snarling
0457081908 Changelog + generate docs 2024-03-11 13:14:33 -04:00
Snarling
162310f005 UI: Fix go colors for light theme (#1155) 2024-03-11 13:13:22 -04:00
Michael Ficocelli
c703b71871 IPVGO: Ensure that the player has the prerequisites to face the secret opponent (#1157) 2024-03-11 12:57:44 -04:00
Snarling
e9d1ddfaf3 SAVEGAME: Reduce size of savefile (#1148)
Storing less info in the save for Factions/Companies if it's still the default info
2024-03-11 08:58:10 -04:00
LJ
4d5401f62e 4 (#1152) 2024-03-10 07:36:20 -04:00
adeilt
de5c1bbf98 DOC: Add namespaced example to disableLog doc. (#1150)
It probably won't be used that often, but "ui.setTheme" seemed the least spoilery function name to use as an example.
2024-03-09 15:25:48 -08:00
User670
c93205fec6 MISC: add a missing word in a comment in crime.ts (#1145) 2024-03-08 11:53:14 -08:00
LJ
09c5ec7769 GANG: Correctly display effects of justice tasks on wanted gain rate (#1144) 2024-03-07 23:46:25 -05:00
Michael Ficocelli
33af6685f8 IPVGO: Fix displayed maximum favor in tooltip to update with player bitnode status (#1143) 2024-03-07 23:46:08 -05:00
Shy
d2dd6916b1 allow json (#1137)
Allow creating .json files.
Also added the json language server so syntax highlighting and validation works with the ingame editor
2024-03-06 15:52:32 -08:00
Snarling
863ac2c8c0 Historical changelog update for hotfix 2024-03-06 16:33:21 -05:00
Michael Ficocelli
1547581c24 IPVGO: Fix self-capture move evaluation (#1140) 2024-03-06 16:23:16 -05:00
Snarling
1755b7cd7f 2.6.1dev initial state 2024-03-05 20:54:11 -05:00
334 changed files with 7482 additions and 8849 deletions

191
.git_blame_ignore_revs Normal file
View File

@@ -0,0 +1,191 @@
## What is this?
## Some git commits contain mostly prettier/lint changes and aren't as valuable when
## using `git blame`. This file contains a list of hashes that identify such commits
## in a way that git can use.
## Tell git to use this file with:
## git config --local blame.ignoreRevsFile .git_blame_ignore_revs
## This file was created by cleaning up and filtering the output of this command line:
## git log --grep "[Pp]rettier"
## All that's really needed here are the actual 40-digit hashes of
## the commits. I left the author/date info in as it helped when I
## was filtering this list to just formatting changes.
4c9ca4990450740785a4dc95a75de53aeac724df
# Author: omuretsu
# Date: Fri Jun 23 10:14:18 2023 -0400
07b1eefe33b16251e0e67ae3231db2059defb1cc
# Author: omuretsu
# Date: Tue Feb 7 21:16:18 2023 -0500
bbe6421b067b0d0b876c42e49dafcbbb6458ef8d
# Author: borisflagell
# Date: Tue Oct 11 15:33:55 2022 +0300
ed0a28d29272ba2d5716618b73d231bca1ad79d1
# Author: hydroflame
# Date: Sun Oct 9 00:22:25 2022 -0400
a1f90d77ce708efb968d7e7ddb804b9862f2f460
# Author: Snarling
# Date: Tue Sep 27 19:58:14 2022 -0400
6be884a9ad42e9920be233302a066dea01766c8e
# Author: borisflagell
# Date: Sat May 28 01:15:05 2022 +0200
51ba358464ce5787ef472531e0bdb2b6b239955e
# Author: Markus-D-M
# Date: Wed May 25 21:27:58 2022 +0200
9091441389182a261438ec16ea17edcf11ab6f39
# Author: borisflagell
# Date: Mon May 23 19:43:46 2022 +0200
5bc1d293ca14f717d967ff4a345fe1ace1ffdd4c
# Author: borisflagell
# Date: Sun May 22 11:59:14 2022 +0200
d44d71712f066518321cefed18e145141ea23818
# Author: borisflagell
# Date: Sun May 22 06:40:32 2022 +0200
bd2d5396a60f31c1bd9837786fb10586cbcc6eda
# Author: phyzical
# Date: Thu Apr 28 19:22:37 2022 +0800
ad4c84be937224297f6745b6df3d433466b047c1
# Author: borisflagell
# Date: Sat Apr 23 15:18:43 2022 +0200
d4f8f2d0354832101f48d60f0b3445cc1f43dd23
# Author: borisflagell
# Date: Sat Apr 23 15:01:24 2022 +0200
a7045a234353de795494c0b3f378808b4a6d32aa
# Author: borisflagell
# Date: Thu Apr 21 19:20:16 2022 +0200
421e7b8c74eb85238ccf45b1a83225a452024058
# Author: borisflagell
# Date: Thu Apr 21 18:51:04 2022 +0200
9850b56711b29c254102ad8694b1610b4d5adc81
# Author: Snarling
# Date: Sun Apr 17 17:23:14 2022 -0400
567fcf8fb63bc469ae61013cca511a76eb6f9086
# Author: violet
# Date: Wed Apr 13 14:42:07 2022 -0500
f6af85a38d8286aec702556f1296565de5a5067f
# Author: violet
# Date: Sun Apr 10 15:57:34 2022 -0500
57f04d3911b5a384c379dcfb2d0bcb18b25c37ea
# Author: violet
# Date: Thu Apr 7 11:45:21 2022 -0500
720e2112c6766b581e43e1f9e712c47300b22dcd
# Author: violet
# Date: Mon Mar 21 14:40:17 2022 -0500
ecffc8655a70e6e3c7bd03ba5723b437d7857dfa
Merge: 8506dcfed 00d1d294e
# Author: hydroflame
# Date: Sun Mar 20 22:21:00 2022 -0400
901ee92fe97606bd27840900512b0fa760ba428e
# Author: Olivier Gagnon
# Date: Sun Mar 20 22:02:36 2022 -0400
b3f9380ebd3feb3b6ef91873a1c6a3a1ddc56654
# Author: Billy Vong
# Date: Fri Dec 17 12:56:48 2021 -0500
# I *really* didn't want to include this one because it has a few tiny, actual
# code changes, but it reformats tens of thousands of lines of code. Argh.
8f13363466c2e825bbfb7e4b66d853c4805eda61
# Author: Olivier Gagnon
# Date: Mon Oct 11 16:38:50 2021 -0400
66a2adaeb4b12a48fc8fdafe5e5169919f8d46a5
# Author: Olivier Gagnon
# Date: Wed Sep 22 12:56:55 2021 -0400
## Additional changes that should be ignored for blame.
2e7f164b5f251ce457f0d3b9a42cfa0f08ef200e
# Author: tyasuh.taeragan
# Date: Fri Oct 22 16:35:05 2021 -0400
a18bdd6afc77752bded2f0794bf35ad9d6455fa1
# Author: Olivier Gagnon
# Date: Sat Sep 4 19:09:30 2021 -0400
# from a search for "lint"; many more of these had significant code changes than
# Prettier, so I'm only vetting the largest for inclusion
48f80f25d6fb90edc292481905814c0d5593b5a9
# Author: Olivier Gagnon
# Date: Wed Apr 6 19:30:08 2022 -0400
b0f20c8c8f4859f01cfc79bc3945a8e7272d6592
# Author: Olivier Gagnon
# Date: Sat Sep 25 01:26:03 2021 -0400
d745150c45979ab251b51447a0847e28966346a6
# Author: Olivier Gagnon
# Date: Sat May 1 03:17:31 2021 -0400
3fad5050961a3259b0158763f48016174a2a690e
# Author: Olivier Gagnon
# Date: Thu Apr 29 23:52:56 2021 -0400
#
# The commits listed below showed up on a search for "[Pp]rettier", but
# weren't just Prettier/lint changes.
#
# 6459b1ab483cf66ae1e6e50518424311c6bbbd8b
# Author: omuretsu
# Date: Sat Feb 4 07:42:35 2023 -0500
# 5b4addbb212411aa1de27ba0ee16ebaabedbc6a1
# Author: borisflagell
# Date: Sun May 22 01:08:53 2022 +0200
# 705a56f3bdcb859179144cdafab7eeb8832f7eb9
# Author: borisflagell
# Date: Sat May 7 12:55:56 2022 +0200
# 4fa65322fe770dc3d79cd0301521d230694a1fc7
# Author: borisflagell
# Date: Thu Apr 21 18:37:47 2022 +0200
# 81d1c02fdfe087c3bd0a2a8a2a41c2f82a6fef56
# Author: hydroflame
# Date: Sun Mar 20 22:50:42 2022 -0400
# 4a7fcda86f5d3d05638b4a9f3a61e5937dde52c6
# Author: hydroflame
# Date: Sat Sep 4 19:03:05 2021 -0400
# 7ee2612c17a90a80e5a0a7793a4c81465e212c0c
# Author: Martin Fournier
# Date: Sun Jan 9 11:15:09 2022 -0500
# c59806c87d543c7cc78d1d68af9599d118c0cb2b
# Author: Martin Fournier
# Date: Fri Dec 17 09:54:32 2021 -0500
# 306facc0d10104d543974d5cb01b6ff419c9acb2
# Author: David Edmondson
# Date: Sat Sep 4 22:17:30 2021 -0700
# 1e42f73e2a1b7ac9489a7287c7be99298f412ac0
# Author: David Edmondson
# Date: Sat Sep 4 13:18:08 2021 -0700commit 2e7f164b5f251ce457f0d3b9a42cfa0f08ef200e

View File

@@ -15,3 +15,5 @@ markdown
package.json
package.lock.json
tsdoc-metadata.json
.git_blame_ignore_revs

View File

@@ -44,6 +44,9 @@ async function createWindow(killall) {
window.removeMenu();
noScripts = killall ? { query: { noScripts: killall } } : {};
window.loadFile("index.html", noScripts);
window.once("ready-to-show", () => {
utils.setZoomFactor(window, utils.getZoomFactor());
});
window.show();
if (debug) window.webContents.openDevTools();
@@ -60,7 +63,6 @@ async function createWindow(killall) {
achievements.enableAchievementsInterval(window);
utils.attachUnresponsiveAppHandler(window);
utils.setZoomFactor(window);
try {
await api.initialize(window);

View File

@@ -1,7 +1,24 @@
/* eslint-disable no-process-exit */
/* eslint-disable @typescript-eslint/no-var-requires */
const { app, dialog, BrowserWindow, ipcMain, protocol } = require("electron");
const log = require("electron-log");
log.catchErrors();
// This handler must be set ASAP to prevent ghost processes.
process.on("uncaughtException", function () {
// The exception will be logged by electron-log.
app.quit();
process.exit(1);
});
// This handler must be set ASAP to prevent ghost processes.
app.on("window-all-closed", () => {
log.info("Quitting the app...");
app.quit();
process.exit(0);
});
const greenworks = require("./greenworks");
const api = require("./api-server");
const gameWindow = require("./gameWindow");
@@ -17,15 +34,8 @@ const { fileURLToPath } = require("url");
log.transports.file.level = store.get("file-log-level", "info");
log.transports.console.level = store.get("console-log-level", "debug");
log.catchErrors();
log.info(`Started app: ${JSON.stringify(process.argv)}`);
process.on("uncaughtException", function () {
// The exception will already have been logged by electron-log
app.quit();
process.exit(1);
});
// We want to fail gracefully if we cannot connect to Steam
try {
if (greenworks && greenworks.init()) {
@@ -42,13 +52,6 @@ try {
let isRestoreDisabled = false;
// This was moved so that startup errors do not lead to ghost processes
app.on("window-all-closed", () => {
log.info("Quitting the app...");
app.quit();
process.exit(0);
});
function setStopProcessHandler(app, window) {
const closingWindowHandler = async (e) => {
// We need to prevent the default closing event to add custom logic
@@ -152,7 +155,7 @@ function setStopProcessHandler(app, window) {
log.debug("Saving to Steam Cloud ...");
try {
const playerId = window.gameInfo.player.identifier;
await storage.pushGameSaveToSteamCloud(save, playerId);
await storage.pushSaveDataToSteamCloud(save, playerId);
log.silly("Saved Game to Steam Cloud");
} catch (error) {
log.error(error);
@@ -203,7 +206,7 @@ app.on("ready", async () => {
if ((method === "GET" && relativePath.startsWith("dist")) || relativePath.match(/^[a-zA-Z-_]*\.html/)) {
return callback(filePath);
}
log.error("Tried to access a page outside sandbox.");
log.error(`Tried to access a page outside the sandbox. Url: ${url}. Method: ${method}.`);
callback(path.join(__dirname, "fileError.txt"));
});

View File

@@ -103,8 +103,8 @@ function getMenu(window) {
enabled: storage.isCloudEnabled(),
click: async () => {
try {
const saveGame = await storage.getSteamCloudSaveString();
await storage.pushSaveGameForImport(window, saveGame, false);
const saveData = await storage.getSteamCloudSaveData();
await storage.pushSaveGameForImport(window, saveData, false);
} catch (error) {
log.error(error);
utils.writeToast(window, "Could not load from Steam Cloud", "error", 5000);
@@ -114,16 +114,6 @@ function getMenu(window) {
{
type: "separator",
},
{
label: "Compress Disk Saves (.gz)",
type: "checkbox",
checked: storage.isSaveCompressionEnabled(),
click: (menuItem) => {
storage.setSaveCompressionConfig(menuItem.checked);
utils.writeToast(window, `${menuItem.checked ? "Enabled" : "Disabled"} Save Compression`, "info", 5000);
refreshMenu(window);
},
},
{
label: "Auto-Save to Disk",
type: "checkbox",
@@ -222,7 +212,7 @@ function getMenu(window) {
click: () => window.loadFile("index.html"),
},
{
label: "Reload & Kill All Scripts",
label: "Reload && Kill All Scripts",
click: () => utils.reloadAndKill(window, true),
},
],

View File

@@ -1,12 +1,12 @@
{
"name": "bitburner",
"version": "2.6.0",
"version": "2.6.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "2.5.2",
"version": "2.6.1",
"dependencies": {
"electron-log": "^4.4.8",
"electron-store": "^8.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "bitburner",
"version": "2.6.0",
"version": "2.6.1",
"description": "A cyberpunk-themed programming incremental game",
"main": "main.js",
"author": "Daniel Xie, hydroflame, et al.",

1
electron/saveDataBinaryFormat.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export declare const isBinaryFormat: (saveData: string | Uint8Array) => boolean;

View File

@@ -0,0 +1,13 @@
// The 2 magic bytes of the gzip header plus the mandatory compression type of DEFLATE
const magicBytes = [0x1f, 0x8b, 0x08];
function isBinaryFormat(rawData) {
for (let i = 0; i < magicBytes.length; ++i) {
if (magicBytes[i] !== rawData[i]) {
return false;
}
}
return true;
}
module.exports = { isBinaryFormat };

View File

@@ -11,6 +11,7 @@ const greenworks = require("./greenworks");
const log = require("electron-log");
const flatten = require("lodash/flatten");
const Store = require("electron-store");
const { isBinaryFormat } = require("./saveDataBinaryFormat");
const store = new Store();
// https://stackoverflow.com/a/69418940
@@ -86,14 +87,6 @@ function isAutosaveEnabled() {
return store.get("autosave-enabled", true);
}
function setSaveCompressionConfig(value) {
store.set("save-compression-enabled", value);
}
function isSaveCompressionEnabled() {
return store.get("save-compression-enabled", true);
}
function setCloudEnabledConfig(value) {
store.set("cloud-enabled", value);
}
@@ -163,17 +156,22 @@ async function backupSteamDataToDisk(currentPlayerId) {
const file = greenworks.getFileNameAndSize(0);
const previousPlayerId = file.name.replace(".json.gz", "");
if (previousPlayerId !== currentPlayerId) {
const backupSave = await getSteamCloudSaveString();
const backupSaveData = await getSteamCloudSaveData();
const backupFile = path.join(app.getPath("userData"), "/saves/_backups", `${previousPlayerId}.json.gz`);
const buffer = Buffer.from(backupSave, "base64").toString("utf8");
saveContent = await gzip(buffer);
await fs.writeFile(backupFile, saveContent, "utf8");
await fs.writeFile(backupFile, backupSaveData, "utf8");
log.debug(`Saved backup game to '${backupFile}`);
}
}
async function pushGameSaveToSteamCloud(base64save, currentPlayerId) {
if (!isCloudEnabled) return Promise.reject("Steam Cloud is not Enabled");
/**
* The name of save file is `${currentPlayerId}.json.gz`. The content of save file is weird: it's a base64 string of the
* binary data of compressed json save string. It's weird because the extension is .json.gz while the content is a
* base64 string. Check the comments in the implementation to see why it is like that.
*/
async function pushSaveDataToSteamCloud(saveData, currentPlayerId) {
if (!isCloudEnabled()) {
return Promise.reject("Steam Cloud is not Enabled");
}
try {
backupSteamDataToDisk(currentPlayerId);
@@ -183,12 +181,19 @@ async function pushGameSaveToSteamCloud(base64save, currentPlayerId) {
const steamSaveName = `${currentPlayerId}.json.gz`;
// Let's decode the base64 string so GZIP is more efficient.
const buffer = Buffer.from(base64save, "base64");
const compressedBuffer = await gzip(buffer);
// We can't use utf8 for some reason, steamworks is unhappy.
const content = compressedBuffer.toString("base64");
log.debug(`Uncompressed: ${base64save.length} bytes`);
/**
* When we push save file to Steam Cloud, we use greenworks.saveTextToFile. It seems that this method expects a string
* as the file content. That is why saveData is encoded in base64 and pushed to Steam Cloud as a text file.
*
* Encoding saveData in UTF-8 (with buffer.toString("utf8")) is not the proper way to convert binary data to string.
* Quote from buffer's documentation: "If encoding is 'utf8' and a byte sequence in the input is not valid UTF-8, then
* each invalid byte is replaced with the replacement character U+FFFD.". The proper way to do it is to use
* String.fromCharCode or String.fromCodePoint.
*
* Instead of implementing it, the old code (encoding in base64) is used here for backward compatibility.
*/
const content = saveData.toString("base64");
log.debug(`Uncompressed: ${saveData.length} bytes`);
log.debug(`Compressed: ${content.length} bytes`);
log.debug(`Saving to Steam Cloud as ${steamSaveName}`);
@@ -199,19 +204,22 @@ async function pushGameSaveToSteamCloud(base64save, currentPlayerId) {
}
}
async function getSteamCloudSaveString() {
if (!isCloudEnabled()) return Promise.reject("Steam Cloud is not Enabled");
/**
* This function processes the save file in Steam Cloud and returns the save data in the binary format.
*/
async function getSteamCloudSaveData() {
if (!isCloudEnabled()) {
return Promise.reject("Steam Cloud is not Enabled");
}
log.debug(`Fetching Save in Steam Cloud`);
const cloudString = await getCloudFile();
const gzippedBase64Buffer = Buffer.from(cloudString, "base64");
const uncompressedBuffer = await gunzip(gzippedBase64Buffer);
const content = uncompressedBuffer.toString("base64");
log.debug(`Compressed: ${cloudString.length} bytes`);
log.debug(`Uncompressed: ${content.length} bytes`);
return content;
// Decode cloudString to get save data back.
const saveData = Buffer.from(cloudString, "base64");
log.debug(`SaveData: ${saveData.length} bytes`);
return saveData;
}
async function saveGameToDisk(window, saveData) {
async function saveGameToDisk(window, electronGameData) {
const currentFolder = await getSaveFolder(window);
let saveFolderSizeBytes = await getFolderSizeInBytes(currentFolder);
const maxFolderSizeBytes = store.get("autosave-quota", 1e8); // 100Mb per playerIndentifier
@@ -221,19 +229,12 @@ async function saveGameToDisk(window, saveData) {
log.debug(
`Remaining: ${remainingSpaceBytes} bytes (${((saveFolderSizeBytes / maxFolderSizeBytes) * 100).toFixed(2)}% used)`,
);
const shouldCompress = isSaveCompressionEnabled();
const fileName = saveData.fileName;
const file = path.join(currentFolder, fileName + (shouldCompress ? ".gz" : ""));
let saveData = electronGameData.save;
const file = path.join(currentFolder, electronGameData.fileName);
try {
let saveContent = saveData.save;
if (shouldCompress) {
// Let's decode the base64 string so GZIP is more efficient.
const buffer = Buffer.from(saveContent, "base64").toString("utf8");
saveContent = await gzip(buffer);
}
await fs.writeFile(file, saveContent, "utf8");
await fs.writeFile(file, saveData, "utf8");
log.debug(`Saved Game to '${file}'`);
log.debug(`Save Size: ${saveContent.length} bytes`);
log.debug(`Save Size: ${saveData.length} bytes`);
} catch (error) {
log.error(error);
}
@@ -276,14 +277,14 @@ async function loadLastFromDisk(window) {
async function loadFileFromDisk(path) {
const buffer = await fs.readFile(path);
let content;
if (path.endsWith(".gz")) {
const uncompressedBuffer = await gunzip(buffer);
content = uncompressedBuffer.toString("base64");
log.debug(`Uncompressed file content (new size: ${content.length} bytes)`);
if (isBinaryFormat(buffer)) {
// Save file is in the binary format.
content = buffer;
} else {
// Save file is in the base64 format.
content = buffer.toString("utf8");
log.debug(`Loaded file with ${content.length} bytes`);
}
log.debug(`Loaded file with ${content.length} bytes`);
return content;
}
@@ -319,7 +320,7 @@ async function restoreIfNewerExists(window) {
const disk = {};
try {
steam.save = await getSteamCloudSaveString();
steam.save = await getSteamCloudSaveData();
steam.data = await getSaveInformation(window, steam.save);
} catch (error) {
log.error("Could not retrieve steam file");
@@ -361,12 +362,12 @@ async function restoreIfNewerExists(window) {
// We add a few seconds to the currentSave's lastSave to prioritize it
log.info("Found newer data than the current's save file");
log.silly(bestMatch.data);
await pushSaveGameForImport(window, bestMatch.save, true);
pushSaveGameForImport(window, bestMatch.save, true);
return true;
} else if (bestMatch.data.playtime > currentData.playtime && currentData.playtime < lowPlaytime) {
log.info("Found older save, but with more playtime, and current less than 15 mins played");
log.silly(bestMatch.data);
await pushSaveGameForImport(window, bestMatch.save, true);
pushSaveGameForImport(window, bestMatch.save, true);
return true;
} else {
log.debug("Current save data is the freshest");
@@ -380,8 +381,8 @@ module.exports = {
getSaveInformation,
restoreIfNewerExists,
pushSaveGameForImport,
pushGameSaveToSteamCloud,
getSteamCloudSaveString,
pushSaveDataToSteamCloud,
getSteamCloudSaveData,
getSteamCloudQuota,
deleteCloudFile,
saveGameToDisk,
@@ -394,6 +395,4 @@ module.exports = {
setCloudEnabledConfig,
isAutosaveEnabled,
setAutosaveConfig,
isSaveCompressionEnabled,
setSaveCompressionConfig,
};

View File

@@ -7,8 +7,12 @@ const store = new Store();
function reloadAndKill(window, killScripts) {
log.info("Reloading & Killing all scripts...");
const zoomFactor = getZoomFactor();
window.webContents.forcefullyCrashRenderer();
window.loadFile("index.html", killScripts ? { query: { noScripts: true } } : {});
window.once("ready-to-show", () => {
setZoomFactor(window, zoomFactor);
});
}
function promptForReload(window) {

View File

@@ -9,17 +9,15 @@ Get current action.
**Signature:**
```typescript
getCurrentAction(): BladeburnerCurAction;
getCurrentAction(): BladeburnerCurAction | null;
```
**Returns:**
[BladeburnerCurAction](./bitburner.bladeburnercuraction.md)
[BladeburnerCurAction](./bitburner.bladeburnercuraction.md) \| null
Object that represents the players current Bladeburner action.
Object that represents the players current Bladeburner action, or null if no action is being performed.
## Remarks
RAM cost: 1 GB
Returns an object that represents the players current Bladeburner action. If the player is not performing an action, the function will return an object with the type property set to “Idle”.

View File

@@ -41,5 +41,6 @@ export interface Corporation extends WarehouseAPI, OfficeAPI
| [levelUpgrade(upgradeName)](./bitburner.corporation.levelupgrade.md) | Level an upgrade. |
| [nextUpdate()](./bitburner.corporation.nextupdate.md) | Sleep until the next Corporation update has happened. |
| [purchaseUnlock(upgradeName)](./bitburner.corporation.purchaseunlock.md) | Unlock an upgrade |
| [sellDivision(divisionName)](./bitburner.corporation.selldivision.md) | Sell a division |
| [sellShares(amount)](./bitburner.corporation.sellshares.md) | Sell Shares. Transfer shares from the CEO to public traders to receive money in the player's wallet. |

View File

@@ -0,0 +1,28 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Corporation](./bitburner.corporation.md) &gt; [sellDivision](./bitburner.corporation.selldivision.md)
## Corporation.sellDivision() method
Sell a division
**Signature:**
```typescript
sellDivision(divisionName: string): void;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| divisionName | string | Name of the division |
**Returns:**
void
## Remarks
RAM cost: 20 GB

View File

@@ -17,5 +17,7 @@ analysis: {
getLiberties(): number[][];
getControlledEmptyNodes(): string[];
getStats(): Partial<Record<GoOpponent, SimpleOpponentStats>>;
};
```

View File

@@ -15,10 +15,9 @@ cheat: {
x: number,
y: number,
): Promise<{
type: "invalid" | "move" | "pass" | "gameOver";
x: number;
y: number;
success: boolean;
type: "move" | "pass" | "gameOver";
x: number | null;
y: number | null;
}>;
playTwoMoves(
x1: number,
@@ -26,30 +25,27 @@ cheat: {
x2: number,
y2: number,
): Promise<{
type: "invalid" | "move" | "pass" | "gameOver";
x: number;
y: number;
success: boolean;
type: "move" | "pass" | "gameOver";
x: number | null;
y: number | null;
}>;
repairOfflineNode(
x: number,
y: number,
): Promise<{
type: "invalid" | "move" | "pass" | "gameOver";
x: number;
y: number;
success: boolean;
type: "move" | "pass" | "gameOver";
x: number | null;
y: number | null;
}>;
destroyNode(
x: number,
y: number,
): Promise<{
type: "invalid" | "move" | "pass" | "gameOver";
x: number;
y: number;
success: boolean;
type: "move" | "pass" | "gameOver";
x: number | null;
y: number | null;
}>;
};
```

View File

@@ -8,15 +8,8 @@ Retrieves a simplified version of the board state. "X" represents black pieces,
For example, a 5x5 board might look like this:
```
[
"XX.O.",
"X..OO",
".XO..",
"XXO.#",
".XO.#",
]
```
\[<br/> "XX.O.",<br/> "X..OO",<br/> ".XO..",<br/> "XXO.\#",<br/> ".XO.\#",<br/> \]
Each string represents a vertical column on the board, and each character in the string represents a point.
Traditional notation for Go is e.g. "B,1" referring to second ("B") column, first rank. This is the equivalent of index \[1\]\[0\].

View File

@@ -0,0 +1,19 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Go](./bitburner.go.md) &gt; [getCurrentPlayer](./bitburner.go.getcurrentplayer.md)
## Go.getCurrentPlayer() method
Returns the color of the current player, or 'None' if the game is over.
**Signature:**
```typescript
getCurrentPlayer(): "White" | "Black" | "None";
```
**Returns:**
"White" \| "Black" \| "None"
"White" \| "Black" \| "None"

View File

@@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Go](./bitburner.go.md) &gt; [getGameState](./bitburner.go.getgamestate.md)
## Go.getGameState() method
Gets the status of the current game. Shows the current player, current score, and the previous move coordinates. Previous move coordinates will be \[-1, -1\] for a pass, or if there are no prior moves.
**Signature:**
```typescript
getGameState(): {
currentPlayer: "White" | "Black" | "None";
whiteScore: number;
blackScore: number;
previousMove: [number, number] | null;
};
```
**Returns:**
{ currentPlayer: "White" \| "Black" \| "None"; whiteScore: number; blackScore: number; previousMove: \[number, number\] \| null; }

View File

@@ -9,9 +9,9 @@ Returns the name of the opponent faction in the current subnet.
**Signature:**
```typescript
getOpponent(): GoOpponent | "No AI" | "????????????";
getOpponent(): GoOpponent | "No AI";
```
**Returns:**
[GoOpponent](./bitburner.goopponent.md) \| "No AI" \| "????????????"
[GoOpponent](./bitburner.goopponent.md) \| "No AI"

View File

@@ -13,10 +13,9 @@ makeMove(
x: number,
y: number,
): Promise<{
type: "invalid" | "move" | "pass" | "gameOver";
x: number;
y: number;
success: boolean;
type: "move" | "pass" | "gameOver";
x: number | null;
y: number | null;
}>;
```
@@ -29,7 +28,7 @@ makeMove(
**Returns:**
Promise&lt;{ type: "invalid" \| "move" \| "pass" \| "gameOver"; x: number; y: number; success: boolean; }&gt;
Promise&lt;{ type: "move" \| "pass" \| "gameOver"; x: number \| null; y: number \| null; }&gt;
a promise that contains if your move was valid and successful, the opponent move's x and y coordinates (or pass) in response, or an indication if the game has ended

View File

@@ -16,26 +16,19 @@ export interface Go
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [analysis](./bitburner.go.analysis.md) | | { getValidMoves(): boolean\[\]\[\]; getChains(): (number \| null)\[\]\[\]; getLiberties(): number\[\]\[\]; getControlledEmptyNodes(): string\[\]; } | Tools to analyze the IPvGO subnet. |
| [cheat](./bitburner.go.cheat.md) | | { getCheatSuccessChance(): number; removeRouter( x: number, y: number, ): Promise&lt;{ type: "invalid" \| "move" \| "pass" \| "gameOver"; x: number; y: number; success: boolean; }&gt;; playTwoMoves( x1: number, y1: number, x2: number, y2: number, ): Promise&lt;{ type: "invalid" \| "move" \| "pass" \| "gameOver"; x: number; y: number; success: boolean; }&gt;; repairOfflineNode( x: number, y: number, ): Promise&lt;{ type: "invalid" \| "move" \| "pass" \| "gameOver"; x: number; y: number; success: boolean; }&gt;; destroyNode( x: number, y: number, ): Promise&lt;{ type: "invalid" \| "move" \| "pass" \| "gameOver"; x: number; y: number; success: boolean; }&gt;; } | Illicit and dangerous IPvGO tools. Not for the faint of heart. Requires Bitnode 14.2 to use. |
| [analysis](./bitburner.go.analysis.md) | | { getValidMoves(): boolean\[\]\[\]; getChains(): (number \| null)\[\]\[\]; getLiberties(): number\[\]\[\]; getControlledEmptyNodes(): string\[\]; getStats(): Partial&lt;Record&lt;[GoOpponent](./bitburner.goopponent.md)<!-- -->, [SimpleOpponentStats](./bitburner.simpleopponentstats.md)<!-- -->&gt;&gt;; } | Tools to analyze the IPvGO subnet. |
| [cheat](./bitburner.go.cheat.md) | | { getCheatSuccessChance(): number; removeRouter( x: number, y: number, ): Promise&lt;{ type: "move" \| "pass" \| "gameOver"; x: number \| null; y: number \| null; }&gt;; playTwoMoves( x1: number, y1: number, x2: number, y2: number, ): Promise&lt;{ type: "move" \| "pass" \| "gameOver"; x: number \| null; y: number \| null; }&gt;; repairOfflineNode( x: number, y: number, ): Promise&lt;{ type: "move" \| "pass" \| "gameOver"; x: number \| null; y: number \| null; }&gt;; destroyNode( x: number, y: number, ): Promise&lt;{ type: "move" \| "pass" \| "gameOver"; x: number \| null; y: number \| null; }&gt;; } | Illicit and dangerous IPvGO tools. Not for the faint of heart. Requires Bitnode 14.2 to use. |
## Methods
| Method | Description |
| --- | --- |
| [getBoardState()](./bitburner.go.getboardstate.md) | <p>Retrieves a simplified version of the board state. "X" represents black pieces, "O" white, and "." empty points. "\#" are dead nodes that are not part of the subnet. (They are not territory nor open nodes.)</p><p>For example, a 5x5 board might look like this:</p>
```
[
"XX.O.",
"X..OO",
".XO..",
"XXO.#",
".XO.#",
]
```
<p>Each string represents a vertical column on the board, and each character in the string represents a point.</p><p>Traditional notation for Go is e.g. "B,1" referring to second ("B") column, first rank. This is the equivalent of index \[1\]\[0\].</p><p>Note that the \[0\]\[0\] point is shown on the bottom-left on the visual board (as is traditional), and each string represents a vertical column on the board. In other words, the printed example above can be understood to be rotated 90 degrees clockwise compared to the board UI as shown in the IPvGO subnet tab.</p> |
| [getBoardState()](./bitburner.go.getboardstate.md) | <p>Retrieves a simplified version of the board state. "X" represents black pieces, "O" white, and "." empty points. "\#" are dead nodes that are not part of the subnet. (They are not territory nor open nodes.)</p><p>For example, a 5x5 board might look like this:</p><p>\[<br/> "XX.O.",<br/> "X..OO",<br/> ".XO..",<br/> "XXO.\#",<br/> ".XO.\#",<br/> \]</p><p>Each string represents a vertical column on the board, and each character in the string represents a point.</p><p>Traditional notation for Go is e.g. "B,1" referring to second ("B") column, first rank. This is the equivalent of index \[1\]\[0\].</p><p>Note that the \[0\]\[0\] point is shown on the bottom-left on the visual board (as is traditional), and each string represents a vertical column on the board. In other words, the printed example above can be understood to be rotated 90 degrees clockwise compared to the board UI as shown in the IPvGO subnet tab.</p> |
| [getCurrentPlayer()](./bitburner.go.getcurrentplayer.md) | Returns the color of the current player, or 'None' if the game is over. |
| [getGameState()](./bitburner.go.getgamestate.md) | Gets the status of the current game. Shows the current player, current score, and the previous move coordinates. Previous move coordinates will be \[-1, -1\] for a pass, or if there are no prior moves. |
| [getOpponent()](./bitburner.go.getopponent.md) | Returns the name of the opponent faction in the current subnet. |
| [makeMove(x, y)](./bitburner.go.makemove.md) | Make a move on the IPvGO subnet gameboard, and await the opponent's response. x:0 y:0 represents the bottom-left corner of the board in the UI. |
| [opponentNextTurn(logOpponentMove)](./bitburner.go.opponentnextturn.md) | Returns a promise that resolves with the success or failure state of your last move, and the AI's response, if applicable. x:0 y:0 represents the bottom-left corner of the board in the UI. |
| [passTurn()](./bitburner.go.passturn.md) | <p>Pass the player's turn rather than making a move, and await the opponent's response. This ends the game if the opponent passed on the previous turn, or if the opponent passes on their following turn.</p><p>This can also be used if you pick up the game in a state where the opponent needs to play next. For example: if BitBurner was closed while waiting for the opponent to make a move, you may need to call passTurn() to get them to play their move on game start.</p> |
| [resetBoardState(opponent, boardSize)](./bitburner.go.resetboardstate.md) | <p>Gets new IPvGO subnet with the specified size owned by the listed faction, ready for the player to make a move. This will reset your win streak if the current game is not complete and you have already made moves.</p><p>Note that some factions will have a few routers on the subnet at this state.</p><p>opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Daedalus" or "Illuminati",</p> |
| [resetBoardState(opponent, boardSize)](./bitburner.go.resetboardstate.md) | <p>Gets new IPvGO subnet with the specified size owned by the listed faction, ready for the player to make a move. This will reset your win streak if the current game is not complete and you have already made moves.</p><p>Note that some factions will have a few routers on the subnet at this state.</p><p>opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????",</p> |

View File

@@ -0,0 +1,34 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Go](./bitburner.go.md) &gt; [opponentNextTurn](./bitburner.go.opponentnextturn.md)
## Go.opponentNextTurn() method
Returns a promise that resolves with the success or failure state of your last move, and the AI's response, if applicable. x:0 y:0 represents the bottom-left corner of the board in the UI.
**Signature:**
```typescript
opponentNextTurn(logOpponentMove?: boolean): Promise<{
type: "move" | "pass" | "gameOver";
x: number | null;
y: number | null;
}>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| logOpponentMove | boolean | _(Optional)_ optional, defaults to true. if false prevents logging opponent move |
**Returns:**
Promise&lt;{ type: "move" \| "pass" \| "gameOver"; x: number \| null; y: number \| null; }&gt;
a promise that contains if your last move was valid and successful, the opponent move's x and y coordinates (or pass) in response, or an indication if the game has ended
## Remarks
RAM cost: 0 GB

View File

@@ -12,15 +12,14 @@ This can also be used if you pick up the game in a state where the opponent need
```typescript
passTurn(): Promise<{
type: "invalid" | "move" | "pass" | "gameOver";
x: number;
y: number;
success: boolean;
type: "move" | "pass" | "gameOver";
x: number | null;
y: number | null;
}>;
```
**Returns:**
Promise&lt;{ type: "invalid" \| "move" \| "pass" \| "gameOver"; x: number; y: number; success: boolean; }&gt;
Promise&lt;{ type: "move" \| "pass" \| "gameOver"; x: number \| null; y: number \| null; }&gt;
a promise that contains if your move was valid and successful, the opponent move's x and y coordinates (or pass) in response, or an indication if the game has ended

View File

@@ -8,7 +8,7 @@ Gets new IPvGO subnet with the specified size owned by the listed faction, ready
Note that some factions will have a few routers on the subnet at this state.
opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Daedalus" or "Illuminati",
opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????",
**Signature:**

View File

@@ -8,5 +8,12 @@
**Signature:**
```typescript
type GoOpponent = "Netburners" | "Slum Snakes" | "The Black Hand" | "Tetrads" | "Daedalus" | "Illuminati";
type GoOpponent =
| "Netburners"
| "Slum Snakes"
| "The Black Hand"
| "Tetrads"
| "Daedalus"
| "Illuminati"
| "????????????";
```

View File

@@ -0,0 +1,33 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [HackingFormulas](./bitburner.hackingformulas.md) &gt; [growAmount](./bitburner.hackingformulas.growamount.md)
## HackingFormulas.growAmount() method
Calculate the amount of money a grow action will leave a server with. Starting money is server.moneyAvailable. Note that when simulating the effect of [grow](./bitburner.ns.grow.md)<!-- -->, what matters is the state of the server and player when the grow \*finishes\*, not when it is started.
The growth amount depends both linearly \*and\* exponentially on threads; see [grow](./bitburner.ns.grow.md) for more details.
The inverse of this function is [formulas.hacking.growThreads](./bitburner.hackingformulas.growthreads.md)<!-- -->, although it rounds up to integer threads.
**Signature:**
```typescript
growAmount(server: Server, player: Person, threads: number, cores?: number): number;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| server | [Server](./bitburner.server.md) | Server info, typically from [getServer](./bitburner.ns.getserver.md) |
| player | [Person](./bitburner.person.md) | Player info, typically from [getPlayer](./bitburner.ns.getplayer.md) |
| threads | number | Number of threads to grow with. Can be fractional. |
| cores | number | _(Optional)_ Number of cores on the computer that will execute grow. |
**Returns:**
number
The amount of money after the calculated grow.

View File

@@ -4,7 +4,13 @@
## HackingFormulas.growPercent() method
Calculate the percent a server would grow to. Not exact due to limitations of mathematics. (Ex: 3.0 would grow the server to 300% of its current value.)
Calculate the growth multiplier constant for a given server and threads.
The actual amount of money grown depends both linearly \*and\* exponentially on threads; this is only giving the exponential part that is used for the multiplier. See [grow](./bitburner.ns.grow.md) for more details.
As a result of the above, this multiplier does \*not\* depend on the amount of money on the server. Changing server.moneyAvailable and server.moneyMax will have no effect.
For the most common use-cases, you probably want either [formulas.hacking.growThreads](./bitburner.hackingformulas.growthreads.md) or [formulas.hacking.growAmount](./bitburner.hackingformulas.growamount.md) instead.
**Signature:**
@@ -17,7 +23,7 @@ growPercent(server: Server, threads: number, player: Person, cores?: number): nu
| Parameter | Type | Description |
| --- | --- | --- |
| server | [Server](./bitburner.server.md) | Server info, typically from [getServer](./bitburner.ns.getserver.md) |
| threads | number | Amount of thread. |
| threads | number | Amount of threads. Can be fractional. |
| player | [Person](./bitburner.person.md) | Player info, typically from [getPlayer](./bitburner.ns.getplayer.md) |
| cores | number | _(Optional)_ Number of cores on the computer that will execute grow. |

View File

@@ -4,7 +4,11 @@
## HackingFormulas.growThreads() method
Calculate how many threads it will take to grow server to targetMoney. Starting money is server.moneyAvailable.
Calculate how many threads it will take to grow server to targetMoney. Starting money is server.moneyAvailable. Note that when simulating the effect of [grow](./bitburner.ns.grow.md)<!-- -->, what matters is the state of the server and player when the grow \*finishes\*, not when it is started.
The growth amount depends both linearly \*and\* exponentially on threads; see [grow](./bitburner.ns.grow.md) for more details.
The inverse of this function is [formulas.hacking.growAmount](./bitburner.hackingformulas.growamount.md)<!-- -->, although it can work with fractional threads.
**Signature:**

View File

@@ -16,8 +16,9 @@ interface HackingFormulas
| Method | Description |
| --- | --- |
| [growPercent(server, threads, player, cores)](./bitburner.hackingformulas.growpercent.md) | Calculate the percent a server would grow to. Not exact due to limitations of mathematics. (Ex: 3.0 would grow the server to 300% of its current value.) |
| [growThreads(server, player, targetMoney, cores)](./bitburner.hackingformulas.growthreads.md) | Calculate how many threads it will take to grow server to targetMoney. Starting money is server.moneyAvailable. |
| [growAmount(server, player, threads, cores)](./bitburner.hackingformulas.growamount.md) | <p>Calculate the amount of money a grow action will leave a server with. Starting money is server.moneyAvailable. Note that when simulating the effect of [grow](./bitburner.ns.grow.md)<!-- -->, what matters is the state of the server and player when the grow \*finishes\*, not when it is started.</p><p>The growth amount depends both linearly \*and\* exponentially on threads; see [grow](./bitburner.ns.grow.md) for more details.</p><p>The inverse of this function is [formulas.hacking.growThreads](./bitburner.hackingformulas.growthreads.md)<!-- -->, although it rounds up to integer threads.</p> |
| [growPercent(server, threads, player, cores)](./bitburner.hackingformulas.growpercent.md) | <p>Calculate the growth multiplier constant for a given server and threads.</p><p>The actual amount of money grown depends both linearly \*and\* exponentially on threads; this is only giving the exponential part that is used for the multiplier. See [grow](./bitburner.ns.grow.md) for more details.</p><p>As a result of the above, this multiplier does \*not\* depend on the amount of money on the server. Changing server.moneyAvailable and server.moneyMax will have no effect.</p><p>For the most common use-cases, you probably want either [formulas.hacking.growThreads](./bitburner.hackingformulas.growthreads.md) or [formulas.hacking.growAmount](./bitburner.hackingformulas.growamount.md) instead.</p> |
| [growThreads(server, player, targetMoney, cores)](./bitburner.hackingformulas.growthreads.md) | <p>Calculate how many threads it will take to grow server to targetMoney. Starting money is server.moneyAvailable. Note that when simulating the effect of [grow](./bitburner.ns.grow.md)<!-- -->, what matters is the state of the server and player when the grow \*finishes\*, not when it is started.</p><p>The growth amount depends both linearly \*and\* exponentially on threads; see [grow](./bitburner.ns.grow.md) for more details.</p><p>The inverse of this function is [formulas.hacking.growAmount](./bitburner.hackingformulas.growamount.md)<!-- -->, although it can work with fractional threads.</p> |
| [growTime(server, player)](./bitburner.hackingformulas.growtime.md) | Calculate grow time. |
| [hackChance(server, player)](./bitburner.hackingformulas.hackchance.md) | Calculate hack chance. (Ex: 0.25 would indicate a 25% chance of success.) |
| [hackExp(server, player)](./bitburner.hackingformulas.hackexp.md) | Calculate hack exp for one thread. |

View File

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

View File

@@ -17,5 +17,7 @@ interface InfiltrationLocation
| --- | --- | --- | --- |
| [difficulty](./bitburner.infiltrationlocation.difficulty.md) | | number | |
| [location](./bitburner.infiltrationlocation.location.md) | | [ILocation](./bitburner.ilocation.md) | |
| [maxClearanceLevel](./bitburner.infiltrationlocation.maxclearancelevel.md) | | number | |
| [reward](./bitburner.infiltrationlocation.reward.md) | | [InfiltrationReward](./bitburner.infiltrationreward.md) | |
| [startingSecurityLevel](./bitburner.infiltrationlocation.startingsecuritylevel.md) | | number | |

View File

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

View File

@@ -162,6 +162,7 @@
| [PlayerRequirement](./bitburner.playerrequirement.md) | Structured interface to requirements for joining a faction or company. For fields with numerical value &gt; 0, the player must have at least this value. For fields with numerical value &lt;<!-- -->= 0, the player must have at most this value. For "not", the sub-condition must be failed instead of passed. For "someCondition", at least one sub-condition must be passed. |
| [ReactNode](./bitburner.reactnode.md) | <p>A stand-in for the real React.ReactNode. A [ReactElement](./bitburner.reactelement.md) is rendered dynamically with React. number and string are displayed directly. boolean, null, and undefined are ignored and not rendered. An array of ReactNodes will display all members of that array sequentially.</p><p>Use React.createElement to make the ReactElement type, see [creating an element without jsx](https://react.dev/reference/react/createElement#creating-an-element-without-jsx) from the official React documentation.</p> |
| [ScriptArg](./bitburner.scriptarg.md) | |
| [SimpleOpponentStats](./bitburner.simpleopponentstats.md) | |
| [SleeveBladeburnerTask](./bitburner.sleevebladeburnertask.md) | |
| [SleeveClassTask](./bitburner.sleeveclasstask.md) | |
| [SleeveCompanyTask](./bitburner.sleevecompanytask.md) | |

View File

@@ -9,7 +9,7 @@ Arguments passed into the script.
**Signature:**
```typescript
readonly args: (string | number | boolean)[];
readonly args: ScriptArg[];
```
## Remarks

View File

@@ -28,3 +28,5 @@ RAM cost: 0 GB
Logging can be disabled for all functions by passing `ALL` as the argument.
For specific interfaces, use the form "namespace.functionName". (e.g. "ui.setTheme")

View File

@@ -9,12 +9,7 @@ Start another script on any server.
**Signature:**
```typescript
exec(
script: string,
hostname: string,
threadOrOptions?: number | RunOptions,
...args: (string | number | boolean)[]
): number;
exec(script: string, hostname: string, threadOrOptions?: number | RunOptions, ...args: ScriptArg[]): number;
```
## Parameters
@@ -24,7 +19,7 @@ exec(
| script | string | Filename of script to execute. This file must already exist on the target server. |
| hostname | string | Hostname of the <code>target server</code> on which to execute the script. |
| threadOrOptions | number \| [RunOptions](./bitburner.runoptions.md) | _(Optional)_ Either an integer number of threads for new script, or a [RunOptions](./bitburner.runoptions.md) object. Threads defaults to 1. |
| args | (string \| number \| boolean)\[\] | Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the third argument threadOrOptions must be filled in with a value. |
| args | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the third argument threadOrOptions must be filled in with a value. |
**Returns:**

View File

@@ -28,6 +28,12 @@ RAM cost: 0 GB
Allows Unix-like flag parsing.
We support 2 forms:
- Short form: the flag contains only 1 character, e.g. -v.
- Long form: the flag contains more than 1 character, e.g. --version.
## Example
@@ -38,21 +44,24 @@ export async function main(ns) {
['server', 'foodnstuff'], // a default string means this flag is a string
['exclude', []], // a default array means this flag is a default array of string
['help', false], // a default boolean means this flag is a boolean
['v', false], // short form
]);
ns.tprint(data);
}
// [home ~/]> run example.js
// {"_":[],"delay":0,"server":"foodnstuff","exclude":[],"help":false}
// {"_":[],"delay":0,"server":"foodnstuff","exclude":[],"help":false,"v":false}
// [home ~/]> run example.js --delay 3000
// {"_":[],"server":"foodnstuff","exclude":[],"help":false,"delay":3000}
// {"_":[],"delay":3000,"server":"foodnstuff","exclude":[],"help":false,"v":false}
// [home ~/]> run example.js --delay 3000 --server harakiri-sushi
// {"_":[],"exclude":[],"help":false,"delay":3000,"server":"harakiri-sushi"}
// {"_":[],"delay":3000,"server":"harakiri-sushi","exclude":[],"help":false,"v":false}
// [home ~/]> run example.js --delay 3000 --server harakiri-sushi hello world
// {"_":["hello","world"],"exclude":[],"help":false,"delay":3000,"server":"harakiri-sushi"}
// {"_":["hello","world"],"delay":3000,"server":"harakiri-sushi","exclude":[],"help":false,"v":false}
// [home ~/]> run example.js --delay 3000 --server harakiri-sushi hello world --exclude a --exclude b
// {"_":["hello","world"],"help":false,"delay":3000,"server":"harakiri-sushi","exclude":["a","b"]}
// [home ~/]> run example.script --help
// {"_":[],"delay":0,"server":"foodnstuff","exclude":[],"help":true}
// {"_":["hello","world"],"delay":3000,"server":"harakiri-sushi","exclude":["a","b"],"help":false,"v":false}
// [home ~/]> run example.js --help
// {"_":[],"delay":0,"server":"foodnstuff","exclude":[],"help":true,"v":false}
// [home ~/]> run example.js -v
// {"_":[],"delay":0,"server":"foodnstuff","exclude":[],"help":false,"v":true}
```

View File

@@ -29,6 +29,6 @@ Returns an object containing the Players hacking related multipliers. These m
```js
const mults = ns.getHackingMultipliers();
print(`chance: ${mults.chance}`);
print(`growthL ${mults.growth}`);
print(`growth: ${mults.growth}`);
```

View File

@@ -9,11 +9,7 @@ Get general info about a running script.
**Signature:**
```typescript
getRunningScript(
filename?: FilenameOrPID,
hostname?: string,
...args: (string | number | boolean)[]
): RunningScript | null;
getRunningScript(filename?: FilenameOrPID, hostname?: string, ...args: ScriptArg[]): RunningScript | null;
```
## Parameters
@@ -22,7 +18,7 @@ getRunningScript(
| --- | --- | --- |
| filename | [FilenameOrPID](./bitburner.filenameorpid.md) | _(Optional)_ Optional. Filename or PID of the script. |
| hostname | string | _(Optional)_ Hostname of target server. Optional, defaults to the server the calling script is running on. |
| args | (string \| number \| boolean)\[\] | Arguments to specify/identify the script. Optional, when looking for scripts run without arguments. |
| args | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Arguments to specify/identify the script. Optional, when looking for scripts run without arguments. |
**Returns:**

View File

@@ -9,7 +9,7 @@ Get the exp gain of a script.
**Signature:**
```typescript
getScriptExpGain(script: string, host: string, ...args: (string | number | boolean)[]): number;
getScriptExpGain(script: string, host: string, ...args: ScriptArg[]): number;
```
## Parameters
@@ -18,7 +18,7 @@ getScriptExpGain(script: string, host: string, ...args: (string | number | boole
| --- | --- | --- |
| script | string | Filename of script. |
| host | string | Server on which script is running. |
| args | (string \| number \| boolean)\[\] | Arguments that the script is running with. |
| args | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Arguments that the script is running with. |
**Returns:**

View File

@@ -9,7 +9,7 @@ Get the income of a script.
**Signature:**
```typescript
getScriptIncome(script: string, host: string, ...args: (string | number | boolean)[]): number;
getScriptIncome(script: string, host: string, ...args: ScriptArg[]): number;
```
## Parameters
@@ -18,7 +18,7 @@ getScriptIncome(script: string, host: string, ...args: (string | number | boolea
| --- | --- | --- |
| script | string | Filename of script. |
| host | string | Server on which script is running. |
| args | (string \| number \| boolean)\[\] | Arguments that the script is running with. |
| args | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Arguments that the script is running with. |
**Returns:**

View File

@@ -9,7 +9,7 @@ Get all the logs of a script.
**Signature:**
```typescript
getScriptLogs(fn?: FilenameOrPID, host?: string, ...args: (string | number | boolean)[]): string[];
getScriptLogs(fn?: FilenameOrPID, host?: string, ...args: ScriptArg[]): string[];
```
## Parameters
@@ -18,7 +18,7 @@ getScriptLogs(fn?: FilenameOrPID, host?: string, ...args: (string | number | boo
| --- | --- | --- |
| fn | [FilenameOrPID](./bitburner.filenameorpid.md) | _(Optional)_ Optional. Filename or PID of script to get logs from. |
| host | string | _(Optional)_ Optional. Hostname of the server that the script is on. |
| args | (string \| number \| boolean)\[\] | Arguments to identify which scripts to get logs for. |
| args | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Arguments to identify which scripts to get logs for. |
**Returns:**

View File

@@ -41,7 +41,9 @@ The multiplier scales exponentially with thread count, and its base depends on t
[growthAnalyze](./bitburner.ns.growthanalyze.md) can be used to determine the number of threads needed for a specified multiplicative portion of server growth.
To determine the effect of a single grow, obtain access to the Formulas API and use [formulas.hacking.growPercent](./bitburner.hackingformulas.growpercent.md)<!-- -->, or invert [growthAnalyze](./bitburner.ns.growthanalyze.md)<!-- -->.
To determine the effect of a single grow, obtain access to the Formulas API and use [formulas.hacking.growPercent](./bitburner.hackingformulas.growamount.md)<!-- -->, or invert [growthAnalyze](./bitburner.ns.growthanalyze.md)<!-- -->.
To determine how many threads are needed to return a server to max money, obtain access to the Formulas API and use [formulas.hacking.growThreads](./bitburner.hackingformulas.growthreads.md)<!-- -->, or [NS.growthAnalyze()](./bitburner.ns.growthanalyze.md) \*if\* the server will be at the same security in the future.
Like [hack](./bitburner.ns.hack.md)<!-- -->, `grow` can be called on any hackable server, regardless of where the script is running. Hackable servers are any servers not owned by the player.

View File

@@ -9,7 +9,7 @@ Check if a script is running.
**Signature:**
```typescript
isRunning(script: FilenameOrPID, host?: string, ...args: (string | number | boolean)[]): boolean;
isRunning(script: FilenameOrPID, host?: string, ...args: ScriptArg[]): boolean;
```
## Parameters
@@ -18,7 +18,7 @@ isRunning(script: FilenameOrPID, host?: string, ...args: (string | number | bool
| --- | --- | --- |
| script | [FilenameOrPID](./bitburner.filenameorpid.md) | Filename or PID of script to check. This is case-sensitive. |
| host | string | _(Optional)_ Hostname of target server. Optional, defaults to the server the calling script is running on. |
| args | (string \| number \| boolean)\[\] | Arguments to specify/identify the script. Optional, when looking for scripts run without arguments. |
| args | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Arguments to specify/identify the script. Optional, when looking for scripts run without arguments. |
**Returns:**

View File

@@ -31,7 +31,7 @@ export async function main(ns) {
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [args](./bitburner.ns.args.md) | <code>readonly</code> | (string \| number \| boolean)\[\] | Arguments passed into the script. |
| [args](./bitburner.ns.args.md) | <code>readonly</code> | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Arguments passed into the script. |
| [bladeburner](./bitburner.ns.bladeburner.md) | <code>readonly</code> | [Bladeburner](./bitburner.bladeburner.md) | Namespace for bladeburner functions. Contains spoilers. |
| [codingcontract](./bitburner.ns.codingcontract.md) | <code>readonly</code> | [CodingContract](./bitburner.codingcontract.md) | Namespace for codingcontract functions. |
| [corporation](./bitburner.ns.corporation.md) | <code>readonly</code> | [Corporation](./bitburner.corporation.md) | Namespace for corporation functions. Contains spoilers. |

View File

@@ -32,7 +32,9 @@ True if the player clicks “Yes”; false if the player clicks “No”; or the
RAM cost: 0 GB
Prompts the player with a dialog box. Here is an explanation of the various options.
Prompts the player with a dialog box and returns a promise. If the player cancels this dialog box (press X button or click outside the dialog box), the promise is resolved with a default value (empty string or "false"). If this API is called again while the old dialog box still exists, the old dialog box will be replaced with a new one, and the old promise will be resolved with the default value.
Here is an explanation of the various options.
- `options.type` is not provided to the function. If `options.type` is left out and only a string is passed to the function, then the default behavior is to create a boolean dialog box.

View File

@@ -9,7 +9,7 @@ Start another script on the current server.
**Signature:**
```typescript
run(script: string, threadOrOptions?: number | RunOptions, ...args: (string | number | boolean)[]): number;
run(script: string, threadOrOptions?: number | RunOptions, ...args: ScriptArg[]): number;
```
## Parameters
@@ -18,7 +18,7 @@ run(script: string, threadOrOptions?: number | RunOptions, ...args: (string | nu
| --- | --- | --- |
| script | string | Filename of script to run. |
| threadOrOptions | number \| [RunOptions](./bitburner.runoptions.md) | _(Optional)_ Either an integer number of threads for new script, or a [RunOptions](./bitburner.runoptions.md) object. Threads defaults to 1. |
| args | (string \| number \| boolean)\[\] | Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the second argument threadOrOptions must be filled in with a value. |
| args | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Additional arguments to pass into the new script that is being run. Note that if any arguments are being passed into the new script, then the second argument threadOrOptions must be filled in with a value. |
**Returns:**

View File

@@ -9,7 +9,7 @@ Terminate current script and start another in a defined number of milliseconds.
**Signature:**
```typescript
spawn(script: string, threadOrOptions?: number | SpawnOptions, ...args: (string | number | boolean)[]): void;
spawn(script: string, threadOrOptions?: number | SpawnOptions, ...args: ScriptArg[]): void;
```
## Parameters
@@ -18,7 +18,7 @@ spawn(script: string, threadOrOptions?: number | SpawnOptions, ...args: (string
| --- | --- | --- |
| script | string | Filename of script to execute. |
| threadOrOptions | number \| [SpawnOptions](./bitburner.spawnoptions.md) | _(Optional)_ Either an integer number of threads for new script, or a [SpawnOptions](./bitburner.spawnoptions.md) object. Threads defaults to 1 and spawnDelay defaults to 10,000 ms. |
| args | (string \| number \| boolean)\[\] | Additional arguments to pass into the new script that is being run. |
| args | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Additional arguments to pass into the new script that is being run. |
**Returns:**

View File

@@ -26,6 +26,8 @@ void
RAM cost: 0.05 GB
Runs the SQLInject.exe program on the target server. SQLInject.exe must exist on your home computer.
## Example

View File

@@ -9,7 +9,7 @@ Open the tail window of a script.
**Signature:**
```typescript
tail(fn?: FilenameOrPID, host?: string, ...args: (string | number | boolean)[]): void;
tail(fn?: FilenameOrPID, host?: string, ...args: ScriptArg[]): void;
```
## Parameters
@@ -18,7 +18,7 @@ tail(fn?: FilenameOrPID, host?: string, ...args: (string | number | boolean)[]):
| --- | --- | --- |
| fn | [FilenameOrPID](./bitburner.filenameorpid.md) | _(Optional)_ Optional. Filename or PID of the script being tailed. If omitted, the current script is tailed. |
| host | string | _(Optional)_ Optional. Hostname of the script being tailed. Defaults to the server this script is running on. If args are specified, this is not optional. |
| args | (string \| number \| boolean)\[\] | Arguments for the script being tailed. |
| args | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Arguments for the script being tailed. |
**Returns:**

View File

@@ -9,5 +9,5 @@ Script's arguments
**Signature:**
```typescript
args: (string | number | boolean)[];
args: ScriptArg[];
```

View File

@@ -16,7 +16,7 @@ interface ProcessInfo
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [args](./bitburner.processinfo.args.md) | | (string \| number \| boolean)\[\] | Script's arguments |
| [args](./bitburner.processinfo.args.md) | | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Script's arguments |
| [filename](./bitburner.processinfo.filename.md) | | string | Script name. |
| [pid](./bitburner.processinfo.pid.md) | | number | Process ID |
| [temporary](./bitburner.processinfo.temporary.md) | | boolean | Whether this process is excluded from saves |

View File

@@ -9,5 +9,5 @@ Arguments the script was called with
**Signature:**
```typescript
args: (string | number | boolean)[];
args: ScriptArg[];
```

View File

@@ -15,7 +15,7 @@ interface RunningScript
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [args](./bitburner.runningscript.args.md) | | (string \| number \| boolean)\[\] | Arguments the script was called with |
| [args](./bitburner.runningscript.args.md) | | [ScriptArg](./bitburner.scriptarg.md)<!-- -->\[\] | Arguments the script was called with |
| [filename](./bitburner.runningscript.filename.md) | | string | Filename of the script |
| [logs](./bitburner.runningscript.logs.md) | | string\[\] | Script logs as an array. The newest log entries are at the bottom. Timestamps, if enabled, are placed inside <code>[brackets]</code> at the start of each line. |
| [offlineExpGained](./bitburner.runningscript.offlineexpgained.md) | | number | Total amount of hacking experience earned from this script when offline |

View File

@@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [SimpleOpponentStats](./bitburner.simpleopponentstats.md)
## SimpleOpponentStats type
**Signature:**
```typescript
type SimpleOpponentStats = {
wins: number;
losses: number;
winStreak: number;
highestWinStreak: number;
favor: number;
bonusPercent: number;
bonusDescription: string;
};
```

View File

@@ -33,7 +33,7 @@ This function will automatically set you to start working on creating the specif
This function returns true if you successfully start working on the specified program, and false otherwise.
Note that creating a program using this function has the same hacking level requirements as it normally would. These level requirements are: \* BruteSSH.exe: 50 \* FTPCrack.exe: 100 \* relaySMTP.exe: 250 \* HTTPWorm.exe: 500 \* SQLInject.exe: 750 \* DeepscanV1.exe: 75 \* DeepscanV2.exe: 400 \* ServerProfiler.exe: 75 \* AutoLink.exe: 25
Note that creating a program using this function has the same hacking level requirements as it normally would. These level requirements are:<br/> - BruteSSH.exe: 50<br/> - FTPCrack.exe: 100<br/> - relaySMTP.exe: 250<br/> - HTTPWorm.exe: 500<br/> - SQLInject.exe: 750<br/> - DeepscanV1.exe: 75<br/> - DeepscanV2.exe: 400<br/> - ServerProfiler.exe: 75<br/> - AutoLink.exe: 25
## Example

View File

@@ -0,0 +1,32 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Singularity](./bitburner.singularity.md) &gt; [getFactionEnemies](./bitburner.singularity.getfactionenemies.md)
## Singularity.getFactionEnemies() method
Get a list of enemies of a faction.
**Signature:**
```typescript
getFactionEnemies(faction: string): string[];
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| faction | string | Name of faction. |
**Returns:**
string\[\]
Array containing the names of all enemies of the faction.
## Remarks
RAM cost: 3 GB \* 16/4/1
Returns an array containing the names (as strings) of all factions that are enemies of the specified faction.

View File

@@ -48,6 +48,7 @@ This API requires Source-File 4 to use. The RAM cost of all these functions is m
| [getCurrentWork()](./bitburner.singularity.getcurrentwork.md) | Get the current work the player is doing. |
| [getDarkwebProgramCost(programName)](./bitburner.singularity.getdarkwebprogramcost.md) | Check the price of an exploit on the dark web |
| [getDarkwebPrograms()](./bitburner.singularity.getdarkwebprograms.md) | Get a list of programs offered on the dark web. |
| [getFactionEnemies(faction)](./bitburner.singularity.getfactionenemies.md) | Get a list of enemies of a faction. |
| [getFactionFavor(faction)](./bitburner.singularity.getfactionfavor.md) | Get faction favor. |
| [getFactionFavorGain(faction)](./bitburner.singularity.getfactionfavorgain.md) | Get faction favor gain. |
| [getFactionInviteRequirements(faction)](./bitburner.singularity.getfactioninviterequirements.md) | List conditions for being invited to a faction. |

View File

@@ -8,5 +8,10 @@
**Signature:**
```typescript
type SleeveInfiltrateTask = { type: "INFILTRATE"; cyclesWorked: number; cyclesNeeded: number };
type SleeveInfiltrateTask = {
type: "INFILTRATE";
cyclesWorked: number;
cyclesNeeded: number;
nextCompletion: Promise<void>;
};
```

508
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "bitburner",
"version": "2.6.0",
"version": "2.6.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "2.6.0",
"version": "2.6.1",
"hasInstallScript": true,
"license": "SEE LICENSE IN license.txt",
"dependencies": {
@@ -24,12 +24,9 @@
"arg": "^5.0.2",
"bcryptjs": "^2.4.3",
"better-react-mathjax": "^2.0.3",
"buffer": "^6.0.3",
"clsx": "^1.2.1",
"date-fns": "^2.30.0",
"escodegen": "^2.1.0",
"file-saver": "^2.0.5",
"jquery": "^3.7.1",
"js-sha256": "^0.9.0",
"jszip": "^3.10.1",
"material-ui-color": "^1.2.0",
@@ -77,10 +74,8 @@
"eslint": "^8.52.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.3.0",
"html-webpack-plugin": "^5.5.3",
"http-server": "^14.1.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jsdom": "^20.0.3",
@@ -92,13 +87,11 @@
"react-refresh": "^0.14.0",
"rehype-mathjax": "^4.0.3",
"remark-math": "^5.1.1",
"source-map": "^0.7.4",
"start-server-and-test": "^1.15.4",
"style-loader": "^3.3.3",
"typescript": "^5.2.2",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
"webpack-dev-server": "^4.15.2"
},
"engines": {
"node": ">=14"
@@ -2428,21 +2421,6 @@
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz",
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
},
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
"dev": true
},
"node_modules/@hapi/topo": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
"dev": true,
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
@@ -4104,27 +4082,6 @@
"string-argv": "~0.3.1"
}
},
"node_modules/@sideway/address": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
"integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
"dev": true,
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@sideway/formula": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
"dev": true
},
"node_modules/@sideway/pinpoint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
"dev": true
},
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -5577,15 +5534,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/async": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"dependencies": {
"lodash": "^4.17.14"
}
},
"node_modules/asynciterator.prototype": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz",
@@ -5631,16 +5579,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/axios": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.14.9",
"form-data": "^4.0.0"
}
},
"node_modules/babel-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
@@ -5944,6 +5882,7 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [
{
"type": "github",
@@ -5959,18 +5898,6 @@
}
]
},
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"dev": true,
"dependencies": {
"safe-buffer": "5.1.2"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/batch": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
@@ -6153,29 +6080,6 @@
"node-int64": "^0.4.0"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@@ -6365,15 +6269,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/check-more-types": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
"integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==",
"dev": true,
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -6757,15 +6652,6 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/corser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
"dev": true,
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -7468,12 +7354,6 @@
"tslib": "^2.0.3"
}
},
"node_modules/duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
"dev": true
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -8276,21 +8156,6 @@
"node": ">= 0.6"
}
},
"node_modules/event-stream": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
"integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==",
"dev": true,
"dependencies": {
"duplexer": "~0.1.1",
"from": "~0",
"map-stream": "~0.1.0",
"pause-stream": "0.0.11",
"split": "0.3",
"stream-combiner": "~0.0.4",
"through": "~2.3.1"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@@ -8592,31 +8457,6 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-loader": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
"integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
"dev": true,
"dependencies": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"node_modules/filename-reserved-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
@@ -9034,12 +8874,6 @@
"node": ">= 0.6"
}
},
"node_modules/from": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
"integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==",
"dev": true
},
"node_modules/fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
@@ -9931,103 +9765,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/http-server": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
"integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
"dev": true,
"dependencies": {
"basic-auth": "^2.0.1",
"chalk": "^4.1.2",
"corser": "^2.0.1",
"he": "^1.2.0",
"html-encoding-sniffer": "^3.0.0",
"http-proxy": "^1.18.1",
"mime": "^1.6.0",
"minimist": "^1.2.6",
"opener": "^1.5.1",
"portfinder": "^1.0.28",
"secure-compare": "3.0.1",
"union": "~0.5.0",
"url-join": "^4.0.1"
},
"bin": {
"http-server": "bin/http-server"
},
"engines": {
"node": ">=12"
}
},
"node_modules/http-server/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/http-server/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/http-server/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/http-server/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/http-server/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/http-server/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
@@ -10092,25 +9829,6 @@
"postcss": "^8.1.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -12535,24 +12253,6 @@
"integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==",
"dev": true
},
"node_modules/joi": {
"version": "17.11.0",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz",
"integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==",
"dev": true,
"dependencies": {
"@hapi/hoek": "^9.0.0",
"@hapi/topo": "^5.0.0",
"@sideway/address": "^4.1.3",
"@sideway/formula": "^3.0.1",
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"node_modules/js-sha256": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
@@ -12863,15 +12563,6 @@
"shell-quote": "^1.8.1"
}
},
"node_modules/lazy-ass": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
"integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==",
"dev": true,
"engines": {
"node": "> 0.8"
}
},
"node_modules/leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@@ -13128,12 +12819,6 @@
"tmpl": "1.0.5"
}
},
"node_modules/map-stream": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
"integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==",
"dev": true
},
"node_modules/markdown-table": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
@@ -14147,18 +13832,6 @@
"resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
"integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/monaco-editor": {
"version": "0.35.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.35.0.tgz",
@@ -14568,15 +14241,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"dev": true,
"bin": {
"opener": "bin/opener-bin.js"
}
},
"node_modules/optionator": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
@@ -14804,15 +14468,6 @@
"node": ">=8"
}
},
"node_modules/pause-stream": {
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
"integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==",
"dev": true,
"dependencies": {
"through": "~2.3"
}
},
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -14971,29 +14626,6 @@
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
"integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA=="
},
"node_modules/portfinder": {
"version": "1.0.32",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
"integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==",
"dev": true,
"dependencies": {
"async": "^2.6.4",
"debug": "^3.2.7",
"mkdirp": "^0.5.6"
},
"engines": {
"node": ">= 0.12.0"
}
},
"node_modules/portfinder/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -15247,21 +14879,6 @@
"node": ">= 0.10"
}
},
"node_modules/ps-tree": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz",
"integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==",
"dev": true,
"dependencies": {
"event-stream": "=3.3.4"
},
"bin": {
"ps-tree": "bin/ps-tree.js"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -16144,15 +15761,6 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@@ -16252,12 +15860,6 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/secure-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
"dev": true
},
"node_modules/select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -16724,18 +16326,6 @@
"node": "^12.20.0 || >=14"
}
},
"node_modules/split": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
"integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==",
"dev": true,
"dependencies": {
"through": "2"
},
"engines": {
"node": "*"
}
},
"node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
@@ -16768,30 +16358,6 @@
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
"dev": true
},
"node_modules/start-server-and-test": {
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.15.4.tgz",
"integrity": "sha512-ucQtp5+UCr0m4aHlY+aEV2JSYNTiMZKdSKK/bsIr6AlmwAWDYDnV7uGlWWEtWa7T4XvRI5cPYcPcQgeLqpz+Tg==",
"dev": true,
"dependencies": {
"arg": "^5.0.2",
"bluebird": "3.7.2",
"check-more-types": "2.24.0",
"debug": "4.3.4",
"execa": "5.1.1",
"lazy-ass": "1.6.0",
"ps-tree": "1.2.0",
"wait-on": "7.0.1"
},
"bin": {
"server-test": "src/bin/start.js",
"start-server-and-test": "src/bin/start.js",
"start-test": "src/bin/start.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -16801,15 +16367,6 @@
"node": ">= 0.8"
}
},
"node_modules/stream-combiner": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
"integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==",
"dev": true,
"dependencies": {
"duplexer": "~0.1.1"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -17195,12 +16752,6 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"dev": true
},
"node_modules/thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@@ -17542,18 +17093,6 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/union": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
"dev": true,
"dependencies": {
"qs": "^6.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/unist-util-find-after": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-4.0.1.tgz",
@@ -17697,12 +17236,6 @@
"punycode": "^2.1.0"
}
},
"node_modules/url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
@@ -17857,25 +17390,6 @@
"node": ">=14"
}
},
"node_modules/wait-on": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz",
"integrity": "sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==",
"dev": true,
"dependencies": {
"axios": "^0.27.2",
"joi": "^17.7.0",
"lodash": "^4.17.21",
"minimist": "^1.2.7",
"rxjs": "^7.8.0"
},
"bin": {
"wait-on": "bin/wait-on"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
@@ -18028,9 +17542,9 @@
}
},
"node_modules/webpack-dev-middleware": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz",
"integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==",
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz",
"integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==",
"dev": true,
"dependencies": {
"colorette": "^2.0.10",
@@ -18104,9 +17618,9 @@
}
},
"node_modules/webpack-dev-server": {
"version": "4.15.1",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
"integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==",
"version": "4.15.2",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz",
"integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==",
"dev": true,
"dependencies": {
"@types/bonjour": "^3.5.9",
@@ -18137,7 +17651,7 @@
"serve-index": "^1.9.1",
"sockjs": "^0.3.24",
"spdy": "^4.0.2",
"webpack-dev-middleware": "^5.3.1",
"webpack-dev-middleware": "^5.3.4",
"ws": "^8.13.0"
},
"bin": {

View File

@@ -1,7 +1,7 @@
{
"name": "bitburner",
"license": "SEE LICENSE IN license.txt",
"version": "2.6.0",
"version": "2.6.1",
"main": "electron-main.js",
"author": {
"name": "Daniel Xie, hydroflame, et al."
@@ -24,12 +24,9 @@
"arg": "^5.0.2",
"bcryptjs": "^2.4.3",
"better-react-mathjax": "^2.0.3",
"buffer": "^6.0.3",
"clsx": "^1.2.1",
"date-fns": "^2.30.0",
"escodegen": "^2.1.0",
"file-saver": "^2.0.5",
"jquery": "^3.7.1",
"js-sha256": "^0.9.0",
"jszip": "^3.10.1",
"material-ui-color": "^1.2.0",
@@ -78,10 +75,8 @@
"eslint": "^8.52.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.3.0",
"html-webpack-plugin": "^5.5.3",
"http-server": "^14.1.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jsdom": "^20.0.3",
@@ -91,13 +86,11 @@
"prettier": "^2.8.8",
"raw-loader": "^4.0.2",
"react-refresh": "^0.14.0",
"source-map": "^0.7.4",
"start-server-and-test": "^1.15.4",
"style-loader": "^3.3.3",
"typescript": "^5.2.2",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"webpack-dev-server": "^4.15.2",
"remark-math": "^5.1.1",
"rehype-mathjax": "^4.0.3"
},

View File

@@ -1,14 +1,13 @@
import {
AugmentationName,
BlackOperationName,
BladeSkillName,
CityName,
CompletedProgramName,
CorpUnlockName,
FactionName,
IndustryType,
} from "@enums";
import { SkillNames } from "../Bladeburner/data/SkillNames";
import { Skills } from "../Bladeburner/Skills";
import { Skills } from "../Bladeburner/data/Skills";
import { CONSTANTS } from "../Constants";
import { Exploit } from "../Exploits/Exploit";
import { Factions } from "../Faction/Factions";
@@ -31,6 +30,7 @@ import { workerScripts } from "../Netscript/WorkerScripts";
import { getRecordValues } from "../Types/Record";
import { ServerConstants } from "../Server/data/Constants";
import { blackOpsArray } from "../Bladeburner/data/BlackOperations";
// Unable to correctly cast the JSON data into AchievementDataJson type otherwise...
const achievementData = (<AchievementDataJson>(<unknown>data)).achievements;
@@ -65,7 +65,7 @@ function bitNodeFinishedState(): boolean {
const wd = GetServer(SpecialServers.WorldDaemon);
if (!(wd instanceof Server)) return false;
if (wd.backdoorInstalled) return true;
return Player.bladeburner !== null && BlackOperationName.OperationDaedalus in Player.bladeburner.blackops;
return Player.bladeburner !== null && Player.bladeburner.numBlackOpsComplete >= blackOpsArray.length;
}
function hasAccessToSF(bn: number): boolean {
@@ -432,8 +432,7 @@ export const achievements: Record<string, Achievement> = {
Icon: "BLADEOVERCLOCK",
Visible: () => hasAccessToSF(6),
Condition: () =>
Player.bladeburner !== null &&
Player.bladeburner.skills[SkillNames.Overclock] === Skills[SkillNames.Overclock].maxLvl,
Player.bladeburner?.getSkillLevel(BladeSkillName.overclock) === Skills[BladeSkillName.overclock].maxLvl,
},
BLADEBURNER_UNSPENT_100000: {
...achievementData.BLADEBURNER_UNSPENT_100000,

View File

@@ -141,16 +141,16 @@ function generateStatsDescription(mults: Multipliers, programs?: string[], start
// Hacknet: costs are negative
if (mults.hacknet_node_money !== 1) desc += `\n+${f(mults.hacknet_node_money - 1)} hacknet production`;
if (mults.hacknet_node_purchase_cost !== 1) {
desc += `\n-${f(-(mults.hacknet_node_purchase_cost - 1))} hacknet nodes cost`;
desc += `\n-${f(-(mults.hacknet_node_purchase_cost - 1))} hacknet purchase cost`;
}
if (mults.hacknet_node_level_cost !== 1) {
desc += `\n-${f(-(mults.hacknet_node_level_cost - 1))} hacknet level upgrade cost`;
}
if (mults.hacknet_node_ram_cost !== 1) {
desc += `\n-${f(-(mults.hacknet_node_ram_cost - 1))} hacknet RAM cost`;
desc += `\n-${f(-(mults.hacknet_node_ram_cost - 1))} hacknet RAM upgrade cost`;
}
if (mults.hacknet_node_core_cost !== 1) {
desc += `\n-${f(-(mults.hacknet_node_core_cost - 1))} hacknet core cost`;
desc += `\n-${f(-(mults.hacknet_node_core_cost - 1))} hacknet core upgrade cost`;
}
// Bladeburner

View File

@@ -390,7 +390,7 @@ export const Augmentations: Record<AugmentationName, Augmentation> = (() => {
moneyCost: 50e12,
info:
"Developed by a pioneer in Grafting research, this implant generates pulses of stability which seem to have a nullifying effect versus the Entropy virus.\n\n" +
"Note: For unknown reasons, the lowercase 'n' appears to be an integral component to its functionality.",
"Note: For unknown reasons, the lowercase 'v' appears to be an integral component to its functionality.",
stats: "This Augmentation removes the Entropy virus, and prevents it from affecting you again.",
factions: [],
},

View File

@@ -86,9 +86,7 @@ function getRandomBonus(): CircadianBonus {
hacknet_node_core_cost: 0.85,
hacknet_node_level_cost: 0.85,
},
description:
"Increases the amount of money produced by Hacknet Nodes by 20%.\n" +
"Decreases all costs related to Hacknet Node by 15%.",
description: "Increases Hacknet production by 20%.\n" + "Decreases all costs related to Hacknet by 15%.",
},
{
bonuses: {

View File

@@ -90,7 +90,7 @@ export enum AugmentationName {
BrachiBlades = "BrachiBlades",
BionicArms = "Bionic Arms",
SNA = "Social Negotiation Assistant (S.N.A)",
CongruityImplant = "nickofolas Congruity Implant",
CongruityImplant = "violet Congruity Implant",
HydroflameLeftArm = "Hydroflame Left Arm",
BigDsBigBrain = "BigD's Big ... Brain",
ZOE = "Z.O.Ë.",

View File

@@ -127,7 +127,7 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
<Typography>- Money</Typography>
<Typography>- Scripts on every computer but your home computer</Typography>
<Typography>- Purchased servers</Typography>
<Typography>- Hacknet Nodes</Typography>
<Typography>- Hacknet</Typography>
<Typography>- Faction/Company reputation</Typography>
<Typography>- Stocks</Typography>
<br />

View File

@@ -23,14 +23,19 @@ interface IBitNodeModifiedStatsProps {
color: string;
}
function customFormatPercent(value: number): string {
return formatPercent(value, 2, 100);
}
function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactElement {
// If player doesn't have SF5 or if the property isn't affected by BitNode mults
if (props.mult === 1 || Player.sourceFileLvl(5) === 0)
return <Typography color={props.color}>{formatPercent(props.base)}</Typography>;
return <Typography color={props.color}>{customFormatPercent(props.base)}</Typography>;
return (
<Typography color={props.color}>
<span style={{ opacity: 0.5 }}>{formatPercent(props.base)}</span> {formatPercent(props.base * props.mult)}
<span style={{ opacity: 0.5 }}>{customFormatPercent(props.base)}</span>{" "}
{customFormatPercent(props.base * props.mult)}
</Typography>
);
}
@@ -192,28 +197,28 @@ export function PlayerMultipliers(): React.ReactElement {
];
const rightColData: MultiplierListItemData[] = [
{
mult: "Hacknet Node Production",
mult: "Hacknet Production",
current: Player.mults.hacknet_node_money,
augmented: Player.mults.hacknet_node_money * mults.hacknet_node_money,
bnMult: currentNodeMults.HacknetNodeMoney,
},
{
mult: "Hacknet Node Purchase Cost",
mult: "Hacknet Purchase Cost",
current: Player.mults.hacknet_node_purchase_cost,
augmented: Player.mults.hacknet_node_purchase_cost * mults.hacknet_node_purchase_cost,
},
{
mult: "Hacknet Node RAM Upgrade Cost",
mult: "Hacknet RAM Upgrade Cost",
current: Player.mults.hacknet_node_ram_cost,
augmented: Player.mults.hacknet_node_ram_cost * mults.hacknet_node_ram_cost,
},
{
mult: "Hacknet Node Core Purchase Cost",
mult: "Hacknet Core Purchase Cost",
current: Player.mults.hacknet_node_core_cost,
augmented: Player.mults.hacknet_node_core_cost * mults.hacknet_node_core_cost,
},
{
mult: "Hacknet Node Level Upgrade Cost",
mult: "Hacknet Level Upgrade Cost",
current: Player.mults.hacknet_node_level_cost,
augmented: Player.mults.hacknet_node_level_cost * mults.hacknet_node_level_cost,
},

View File

@@ -463,20 +463,20 @@ export function initBitNodes() {
its level up to a maximum of 3. This Source-File grants the following benefits:
<br />
<br />
Level 1: 25% increased stat multipliers from Node Power
Level 1: 100% increased stat multipliers from Node Power
<br />
Level 2: Permanently unlocks the go.cheat API
<br />
Level 3: 25% increased success rate for the go.cheat API
Level 3: 25% additive increased success rate for the go.cheat API
<br />
<br />
This Source-File also increases the maximum favor you can gain for each faction from IPvGO by:
This Source-File also increases the maximum favor you can gain for each faction from IPvGO to:
<br />
Level 1: +10
Level 1: 80
<br />
Level 2: +20
Level 2: 100
<br />
Level 3: +40
Level 3: 120
</>
),
);

View File

@@ -43,7 +43,7 @@ export const BitNodeMultipliersDisplay = ({ n, level }: IProps): React.ReactElem
const mults = getBitNodeMultipliers(n, level ?? Math.min(Player.sourceFileLvl(n) + 1, maxSfLevel));
return (
<Box sx={{ columnCount: 2, columnGap: 1, mb: -2 }}>
<Box sx={{ columnCount: 2, columnGap: 1, mb: n === 1 ? 0 : -2 }}>
<GeneralMults n={n} mults={mults} />
<SkillMults n={n} mults={mults} />
<FactionMults n={n} mults={mults} />

View File

@@ -241,7 +241,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | / __| \ | | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | O | | O / | O | | O | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | |_/ |/ | \_ \_| | | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | | <BitNodePortal n={14} level={n(4)} flume={props.flume} destroyedBitNode={destroyed} /> | | O__/ | / \__ | | O | | | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | | <BitNodePortal n={14} level={n(14)} flume={props.flume} destroyedBitNode={destroyed} /> | | O__/ | / \__ | | O | | | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | / /| O / \| | | | | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>O | | | \| | O / _/ | / O | |/ | | | O</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | O / | O O | | \ O| | | |</Typography>

View File

@@ -1,306 +0,0 @@
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { addOffset } from "../utils/helpers/addOffset";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { BladeburnerConstants } from "./data/Constants";
import { Bladeburner } from "./Bladeburner";
import { Person } from "../PersonObjects/Person";
import { calculateIntelligenceBonus } from "../PersonObjects/formulas/intelligence";
interface ISuccessChanceParams {
est: boolean;
}
class StatsMultiplier {
[key: string]: number;
hack = 0;
str = 0;
def = 0;
dex = 0;
agi = 0;
cha = 0;
int = 0;
}
export interface IActionParams {
name?: string;
level?: number;
maxLevel?: number;
autoLevel?: boolean;
baseDifficulty?: number;
difficultyFac?: number;
rewardFac?: number;
successes?: number;
failures?: number;
rankGain?: number;
rankLoss?: number;
hpLoss?: number;
hpLost?: number;
isStealth?: boolean;
isKill?: boolean;
count?: number;
weights?: StatsMultiplier;
decays?: StatsMultiplier;
teamCount?: number;
}
export class Action {
name = "";
// Difficulty scales with level. See getDifficulty() method
level = 1;
maxLevel = 1;
autoLevel = true;
baseDifficulty = 100;
difficultyFac = 1.01;
// Rank increase/decrease is affected by this exponent
rewardFac = 1.02;
successes = 0;
failures = 0;
// All of these scale with level/difficulty
rankGain = 0;
rankLoss = 0;
hpLoss = 0;
hpLost = 0;
// Action Category. Current categories are stealth and kill
isStealth = false;
isKill = false;
/**
* Number of this contract remaining, and its growth rate
* Growth rate is an integer and the count will increase by that integer every "cycle"
*/
count: number = getRandomInt(1e3, 25e3);
// Weighting of each stat in determining action success rate
weights: StatsMultiplier = {
hack: 1 / 7,
str: 1 / 7,
def: 1 / 7,
dex: 1 / 7,
agi: 1 / 7,
cha: 1 / 7,
int: 1 / 7,
};
// Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1)
decays: StatsMultiplier = {
hack: 0.9,
str: 0.9,
def: 0.9,
dex: 0.9,
agi: 0.9,
cha: 0.9,
int: 0.9,
};
teamCount = 0;
// Base Class for Contracts, Operations, and BlackOps
constructor(params: IActionParams | null = null) {
// | null = null
if (params && params.name) this.name = params.name;
if (params && params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10);
if (params && params.difficultyFac) this.difficultyFac = params.difficultyFac;
if (params && params.rewardFac) this.rewardFac = params.rewardFac;
if (params && params.rankGain) this.rankGain = params.rankGain;
if (params && params.rankLoss) this.rankLoss = params.rankLoss;
if (params && params.hpLoss) this.hpLoss = params.hpLoss;
if (params && params.isStealth) this.isStealth = params.isStealth;
if (params && params.isKill) this.isKill = params.isKill;
if (params && params.count) this.count = params.count;
if (params && params.weights) this.weights = params.weights;
if (params && params.decays) this.decays = params.decays;
// Check to make sure weights are summed properly
let sum = 0;
for (const weight of Object.keys(this.weights)) {
if (Object.hasOwn(this.weights, weight)) {
sum += this.weights[weight];
}
}
if (sum - 1 >= 10 * Number.EPSILON) {
throw new Error(
"Invalid weights when constructing Action " +
this.name +
". The weights should sum up to 1. They sum up to :" +
1,
);
}
for (const decay of Object.keys(this.decays)) {
if (Object.hasOwn(this.decays, decay)) {
if (this.decays[decay] > 1) {
throw new Error(`Invalid decays when constructing Action ${this.name}. Decay value cannot be greater than 1`);
}
}
}
}
getDifficulty(): number {
const difficulty = this.baseDifficulty * Math.pow(this.difficultyFac, this.level - 1);
if (isNaN(difficulty)) {
throw new Error("Calculated NaN in Action.getDifficulty()");
}
return difficulty;
}
/**
* Tests for success. Should be called when an action has completed
* @param inst {Bladeburner} - Bladeburner instance
*/
attempt(inst: Bladeburner, person: Person): boolean {
return Math.random() < this.getSuccessChance(inst, person);
}
// To be implemented by subtypes
getActionTimePenalty(): number {
return 1;
}
getActionTime(inst: Bladeburner, person: Person): number {
const difficulty = this.getDifficulty();
let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor;
const skillFac = inst.skillMultipliers.actionTime; // Always < 1
const effAgility = person.skills.agility * inst.skillMultipliers.effAgi;
const effDexterity = person.skills.dexterity * inst.skillMultipliers.effDex;
const statFac =
0.5 *
(Math.pow(effAgility, BladeburnerConstants.EffAgiExponentialFactor) +
Math.pow(effDexterity, BladeburnerConstants.EffDexExponentialFactor) +
effAgility / BladeburnerConstants.EffAgiLinearFactor +
effDexterity / BladeburnerConstants.EffDexLinearFactor); // Always > 1
baseTime = Math.max(1, (baseTime * skillFac) / statFac);
return Math.ceil(baseTime * this.getActionTimePenalty());
}
// Subtypes of Action implement these differently
getTeamSuccessBonus(__inst: Bladeburner): number {
return 1;
}
getActionTypeSkillSuccessBonus(__inst: Bladeburner): number {
return 1;
}
getChaosCompetencePenalty(inst: Bladeburner, params: ISuccessChanceParams): number {
const city = inst.getCurrentCity();
if (params.est) {
return Math.pow(city.popEst / BladeburnerConstants.PopulationThreshold, BladeburnerConstants.PopulationExponent);
} else {
return Math.pow(city.pop / BladeburnerConstants.PopulationThreshold, BladeburnerConstants.PopulationExponent);
}
}
getChaosDifficultyBonus(inst: Bladeburner /*, params: ISuccessChanceParams*/): number {
const city = inst.getCurrentCity();
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
const mult = Math.pow(diff, 0.5);
return mult;
}
return 1;
}
getEstSuccessChance(inst: Bladeburner, person: Person): [number, number] {
function clamp(x: number): number {
return Math.max(0, Math.min(x, 1));
}
const est = this.getSuccessChance(inst, person, { est: true });
const real = this.getSuccessChance(inst, person);
const diff = Math.abs(real - est);
let low = real - diff;
let high = real + diff;
const city = inst.getCurrentCity();
let r = city.pop / city.popEst;
if (Number.isNaN(r)) r = 0;
if (r < 1) low *= r;
else high *= r;
return [clamp(low), clamp(high)];
}
/**
* @inst - Bladeburner Object
* @params - options:
* est (bool): Get success chance estimate instead of real success chance
*/
getSuccessChance(inst: Bladeburner, person: Person, params: ISuccessChanceParams = { est: false }): number {
if (inst == null) {
throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance");
}
let difficulty = this.getDifficulty();
let competence = 0;
for (const stat of Object.keys(this.weights)) {
if (Object.hasOwn(this.weights, stat)) {
const playerStatLvl = person.queryStatFromString(stat);
const key = "eff" + stat.charAt(0).toUpperCase() + stat.slice(1);
let effMultiplier = inst.skillMultipliers[key];
if (effMultiplier == null) {
console.error(`Failed to find Bladeburner Skill multiplier for: ${stat}`);
effMultiplier = 1;
}
competence += this.weights[stat] * Math.pow(effMultiplier * playerStatLvl, this.decays[stat]);
}
}
competence *= calculateIntelligenceBonus(person.skills.intelligence, 0.75);
competence *= inst.calculateStaminaPenalty();
competence *= this.getTeamSuccessBonus(inst);
competence *= this.getChaosCompetencePenalty(inst, params);
difficulty *= this.getChaosDifficultyBonus(inst);
if (this.name == "Raid" && inst.getCurrentCity().comms <= 0) {
return 0;
}
// Factor skill multipliers into success chance
competence *= inst.skillMultipliers.successChanceAll;
competence *= this.getActionTypeSkillSuccessBonus(inst);
if (this.isStealth) {
competence *= inst.skillMultipliers.successChanceStealth;
}
if (this.isKill) {
competence *= inst.skillMultipliers.successChanceKill;
}
// Augmentation multiplier
competence *= person.mults.bladeburner_success_chance;
if (isNaN(competence)) {
throw new Error("Competence calculated as NaN in Action.getSuccessChance()");
}
return Math.min(1, competence / difficulty);
}
getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number {
return Math.ceil(0.5 * this.maxLevel * (2 * baseSuccessesPerLevel + (this.maxLevel - 1)));
}
setMaxLevel(baseSuccessesPerLevel: number): void {
if (this.successes >= this.getSuccessesNeededForNextLevel(baseSuccessesPerLevel)) {
++this.maxLevel;
}
}
toJSON(): IReviverValue {
return Generic_toJSON("Action", this);
}
static fromJSON(value: IReviverValue): Action {
return Generic_fromJSON(Action, value.data);
}
}
constructorsForReviver.Action = Action;

View File

@@ -1,26 +0,0 @@
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
interface IParams {
name?: string;
type?: number;
}
export class ActionIdentifier {
name = "";
type = -1;
constructor(params: IParams = {}) {
if (params.name) this.name = params.name;
if (params.type) this.type = params.type;
}
toJSON(): IReviverValue {
return Generic_toJSON("ActionIdentifier", this);
}
static fromJSON(value: IReviverValue): ActionIdentifier {
return Generic_fromJSON(ActionIdentifier, value.data);
}
}
constructorsForReviver.ActionIdentifier = ActionIdentifier;

View File

@@ -0,0 +1,183 @@
import type { Bladeburner } from "../Bladeburner";
import type { Person } from "../../PersonObjects/Person";
import type { Availability, SuccessChanceParams } from "../Types";
import type { Skills as PersonSkills } from "../../PersonObjects/Skills";
import { addOffset } from "../../utils/helpers/addOffset";
import { BladeburnerConstants } from "../data/Constants";
import { calculateIntelligenceBonus } from "../../PersonObjects/formulas/intelligence";
import { BladeMultName } from "../Enums";
import { getRecordKeys } from "../../Types/Record";
export interface ActionParams {
desc: string;
baseDifficulty?: number;
rewardFac?: number;
rankGain?: number;
rankLoss?: number;
hpLoss?: number;
isStealth?: boolean;
isKill?: boolean;
weights?: PersonSkills;
decays?: PersonSkills;
}
export abstract class ActionClass {
desc = "";
// For LevelableActions, the base difficulty can be increased based on action level
baseDifficulty = 100;
// All of these scale with level/difficulty
rankGain = 0;
rankLoss = 0;
hpLoss = 0;
// Action Category. Current categories are stealth and kill
isStealth = false;
isKill = false;
// Weighting of each stat in determining action success rate
weights: PersonSkills = {
hacking: 1 / 7,
strength: 1 / 7,
defense: 1 / 7,
dexterity: 1 / 7,
agility: 1 / 7,
charisma: 1 / 7,
intelligence: 1 / 7,
};
// Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1)
decays: PersonSkills = {
hacking: 0.9,
strength: 0.9,
defense: 0.9,
dexterity: 0.9,
agility: 0.9,
charisma: 0.9,
intelligence: 0.9,
};
constructor(params: ActionParams | null = null) {
if (!params) return;
this.desc = params.desc;
if (params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10);
if (params.rankGain) this.rankGain = params.rankGain;
if (params.rankLoss) this.rankLoss = params.rankLoss;
if (params.hpLoss) this.hpLoss = params.hpLoss;
if (params.isStealth) this.isStealth = params.isStealth;
if (params.isKill) this.isKill = params.isKill;
if (params.weights) this.weights = params.weights;
if (params.decays) this.decays = params.decays;
}
/** Tests for success. Should be called when an action has completed */
attempt(bladeburner: Bladeburner, person: Person): boolean {
return Math.random() < this.getSuccessChance(bladeburner, person);
}
// All the functions below are overwritten by certain subtypes of action, e.g. BlackOps ignore city stats
getPopulationSuccessFactor(bladeburner: Bladeburner, { est }: SuccessChanceParams): number {
const city = bladeburner.getCurrentCity();
const pop = est ? city.popEst : city.pop;
return Math.pow(pop / BladeburnerConstants.PopulationThreshold, BladeburnerConstants.PopulationExponent);
}
getChaosSuccessFactor(bladeburner: Bladeburner): number {
const city = bladeburner.getCurrentCity();
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
const mult = Math.pow(diff, 0.5);
return mult;
}
return 1;
}
getActionTime(bladeburner: Bladeburner, person: Person): number {
const difficulty = this.getDifficulty();
let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor;
const skillFac = bladeburner.getSkillMult(BladeMultName.actionTime); // Always < 1
const effAgility = bladeburner.getEffectiveSkillLevel(person, "agility");
const effDexterity = bladeburner.getEffectiveSkillLevel(person, "dexterity");
const statFac =
0.5 *
(Math.pow(effAgility, BladeburnerConstants.EffAgiExponentialFactor) +
Math.pow(effDexterity, BladeburnerConstants.EffDexExponentialFactor) +
effAgility / BladeburnerConstants.EffAgiLinearFactor +
effDexterity / BladeburnerConstants.EffDexLinearFactor); // Always > 1
baseTime = Math.max(1, (baseTime * skillFac) / statFac);
return Math.ceil(baseTime * this.getActionTimePenalty());
}
getTeamSuccessBonus(__bladeburner: Bladeburner): number {
return 1;
}
getActionTypeSkillSuccessBonus(__bladeburner: Bladeburner): number {
return 1;
}
getAvailability(__bladeburner: Bladeburner): Availability {
return { available: true };
}
getActionTimePenalty(): number {
return 1;
}
getDifficulty(): number {
return this.baseDifficulty;
}
getSuccessRange(bladeburner: Bladeburner, person: Person): [minChance: number, maxChance: number] {
function clamp(x: number): number {
return Math.max(0, Math.min(x, 1));
}
const est = this.getSuccessChance(bladeburner, person, { est: true });
const real = this.getSuccessChance(bladeburner, person);
const diff = Math.abs(real - est);
let low = real - diff;
let high = real + diff;
const city = bladeburner.getCurrentCity();
let r = city.pop / city.popEst;
if (Number.isNaN(r)) r = 0;
if (r < 1) low *= r;
else high *= r;
return [clamp(low), clamp(high)];
}
getSuccessChance(inst: Bladeburner, person: Person, { est }: SuccessChanceParams = { est: false }): number {
let difficulty = this.getDifficulty();
let competence = 0;
for (const stat of getRecordKeys(person.skills)) {
competence += this.weights[stat] * Math.pow(inst.getEffectiveSkillLevel(person, stat), this.decays[stat]);
}
competence *= calculateIntelligenceBonus(person.skills.intelligence, 0.75);
competence *= inst.calculateStaminaPenalty();
competence *= this.getTeamSuccessBonus(inst);
competence *= this.getPopulationSuccessFactor(inst, { est });
difficulty *= this.getChaosSuccessFactor(inst);
// Factor skill multipliers into success chance
competence *= inst.getSkillMult(BladeMultName.successChanceAll);
competence *= this.getActionTypeSkillSuccessBonus(inst);
if (this.isStealth) competence *= inst.getSkillMult(BladeMultName.successChanceStealth);
if (this.isKill) competence *= inst.getSkillMult(BladeMultName.successChanceKill);
// Augmentation multiplier
competence *= person.mults.bladeburner_success_chance;
if (isNaN(competence)) {
throw new Error("Competence calculated as NaN in Action.getSuccessChance()");
}
return Math.min(1, competence / difficulty);
}
}

View File

@@ -0,0 +1,51 @@
import type { Bladeburner } from "../Bladeburner";
import type { Availability, ActionIdentifier } from "../Types";
import { BladeActionType, BladeBlackOpName } from "@enums";
import { ActionClass, ActionParams } from "./Action";
import { operationSkillSuccessBonus, operationTeamSuccessBonus } from "./Operation";
interface BlackOpParams {
name: BladeBlackOpName;
reqdRank: number;
n: number;
}
export class BlackOperation extends ActionClass {
type: BladeActionType.blackOp = BladeActionType.blackOp;
name: BladeBlackOpName;
n: number;
reqdRank: number;
teamCount = 0;
get id(): ActionIdentifier {
return { type: this.type, name: this.name };
}
constructor(params: ActionParams & BlackOpParams) {
super(params);
this.name = params.name;
this.reqdRank = params.reqdRank;
this.n = params.n;
}
getAvailability(bladeburner: Bladeburner): Availability {
if (bladeburner.numBlackOpsComplete < this.n) return { error: "Have not completed the previous Black Operation" };
if (bladeburner.numBlackOpsComplete > this.n) return { error: "Already completed" };
if (bladeburner.rank < this.reqdRank) return { error: "Insufficient rank" };
return { available: true };
}
// To be implemented by subtypes
getActionTimePenalty(): number {
return 1.5;
}
getPopulationSuccessFactor(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
return 1;
}
getChaosSuccessFactor(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
return 1;
}
getTeamSuccessBonus = operationTeamSuccessBonus;
getActionTypeSkillSuccessBonus = operationSkillSuccessBonus;
}

View File

@@ -0,0 +1,33 @@
import type { Bladeburner } from "../Bladeburner";
import type { ActionIdentifier } from "../Types";
import { Generic_fromJSON, IReviverValue, constructorsForReviver } from "../../utils/JSONReviver";
import { BladeActionType, BladeContractName, BladeMultName } from "../Enums";
import { LevelableActionClass, LevelableActionParams } from "./LevelableAction";
export class Contract extends LevelableActionClass {
type: BladeActionType.contract = BladeActionType.contract;
name: BladeContractName = BladeContractName.tracking;
get id(): ActionIdentifier {
return { type: this.type, name: this.name };
}
constructor(params: (LevelableActionParams & { name: BladeContractName }) | null = null) {
super(params);
if (params) this.name = params.name;
}
getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
return inst.getSkillMult(BladeMultName.successChanceContract);
}
toJSON(): IReviverValue {
return this.save("Contract");
}
static fromJSON(value: IReviverValue): Contract {
return Generic_fromJSON(Contract, value.data);
}
}
constructorsForReviver.Contract = Contract;

View File

@@ -0,0 +1,35 @@
import type { Person } from "../../PersonObjects/Person";
import type { Bladeburner } from "../Bladeburner";
import type { ActionIdentifier } from "../Types";
import { BladeActionType, BladeGeneralActionName } from "@enums";
import { ActionClass, ActionParams } from "./Action";
type GeneralActionParams = ActionParams & {
name: BladeGeneralActionName;
getActionTime: (bladeburner: Bladeburner, person: Person) => number;
getSuccessChance?: (bladeburner: Bladeburner, person: Person) => number;
};
export class GeneralAction extends ActionClass {
type: BladeActionType.general = BladeActionType.general;
name: BladeGeneralActionName;
get id(): ActionIdentifier {
return { type: this.type, name: this.name };
}
constructor(params: GeneralActionParams) {
super(params);
this.name = params.name;
this.getActionTime = params.getActionTime;
if (params.getSuccessChance) this.getSuccessChance = params.getSuccessChance;
}
getSuccessChance(__bladeburner: Bladeburner, __person: Person): number {
return 1;
}
getSuccessRange(bladeburner: Bladeburner, person: Person): [minChance: number, maxChance: number] {
const chance = this.getSuccessChance(bladeburner, person);
return [chance, chance];
}
}

View File

@@ -0,0 +1,111 @@
import type { Bladeburner } from "../Bladeburner";
import type { IReviverValue } from "../../utils/JSONReviver";
import type { Availability } from "../Types";
import { ActionClass, ActionParams } from "./Action";
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
import { clampInteger } from "../../utils/helpers/clampNumber";
export type LevelableActionParams = ActionParams & {
growthFunction: () => number;
difficultyFac?: number;
rewardFac?: number;
minCount?: number;
maxCount?: number;
};
export abstract class LevelableActionClass extends ActionClass {
// Static info, not included in save
difficultyFac = 1.01;
rewardFac = 1.02;
growthFunction = () => 0;
minCount = 1;
maxCount = 150;
// Dynamic properties included in save
count = 0;
level = 1;
maxLevel = 1;
autoLevel = true;
successes = 0;
failures = 0;
constructor(params: LevelableActionParams | null = null) {
super(params);
if (!params) return;
if (params.minCount) this.minCount = params.minCount;
if (params.maxCount) this.maxCount = params.maxCount;
if (params.difficultyFac) this.difficultyFac = params.difficultyFac;
if (params.rewardFac) this.rewardFac = params.rewardFac;
this.count = getRandomIntInclusive(this.minCount, this.maxCount);
this.growthFunction = params.growthFunction;
}
getAvailability(__bladeburner: Bladeburner): Availability {
if (this.count < 1) return { error: "Insufficient action count" };
return { available: true };
}
setMaxLevel(baseSuccessesPerLevel: number): void {
if (this.successes >= this.getSuccessesNeededForNextLevel(baseSuccessesPerLevel)) {
++this.maxLevel;
}
}
getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number {
return Math.ceil(0.5 * this.maxLevel * (2 * baseSuccessesPerLevel + (this.maxLevel - 1)));
}
getDifficulty() {
const difficulty = this.baseDifficulty * Math.pow(this.difficultyFac, this.level - 1);
if (isNaN(difficulty)) {
throw new Error("Calculated NaN in Action.getDifficulty()");
}
return difficulty;
}
/** Reset a levelable action's tracked stats */
reset() {
this.count = getRandomIntInclusive(this.minCount, this.maxCount);
this.level = 1;
this.maxLevel = 1;
this.autoLevel = true;
this.successes = 0;
this.failures = 0;
}
/** These are not loaded the same way as most game objects, to allow better typechecking on load + partially static loading */
loadData(loadedObject: LevelableActionClass) {
this.maxLevel = clampInteger(loadedObject.maxLevel, 1);
this.level = clampInteger(loadedObject.level, 1, this.maxLevel);
this.count = clampInteger(loadedObject.count, 0);
this.autoLevel = !!loadedObject.autoLevel;
this.successes = clampInteger(loadedObject.successes, 0);
this.failures = clampInteger(loadedObject.failures, 0);
}
/** Create a basic object just containing the relevant data for a levelable action */
save<T extends LevelableActionClass>(
this: T,
ctorName: string,
...extraParams: (keyof T)[]
): IReviverValue<LevelableActionSaveData> {
const data = {
...Object.fromEntries(extraParams.map((param) => [param, this[param]])),
count: this.count,
level: this.level,
maxLevel: this.maxLevel,
autoLevel: this.autoLevel,
successes: this.successes,
failures: this.failures,
};
return { ctor: ctorName, data };
}
}
export interface LevelableActionSaveData {
count: number;
level: number;
maxLevel: number;
autoLevel: boolean;
successes: number;
failures: number;
}

View File

@@ -0,0 +1,84 @@
import type { Person } from "../../PersonObjects/Person";
import type { BlackOperation } from "./BlackOperation";
import type { Bladeburner } from "../Bladeburner";
import type { Availability, ActionIdentifier, SuccessChanceParams } from "../Types";
import { BladeActionType, BladeMultName, BladeOperationName } from "@enums";
import { BladeburnerConstants } from "../data/Constants";
import { ActionClass } from "./Action";
import { Generic_fromJSON, IReviverValue, constructorsForReviver } from "../../utils/JSONReviver";
import { LevelableActionClass, LevelableActionParams } from "./LevelableAction";
import { clampInteger } from "../../utils/helpers/clampNumber";
export interface OperationParams extends LevelableActionParams {
name: BladeOperationName;
getAvailability?: (bladeburner: Bladeburner) => Availability;
}
export class Operation extends LevelableActionClass {
type: BladeActionType.operation = BladeActionType.operation;
name = BladeOperationName.investigation;
teamCount = 0;
get id(): ActionIdentifier {
return { type: this.type, name: this.name };
}
constructor(params: OperationParams | null = null) {
super(params);
if (!params) return;
this.name = params.name;
if (params.getAvailability) this.getAvailability = params.getAvailability;
}
// These functions are shared between operations and blackops, so they are defined outside of Operation
getTeamSuccessBonus = operationTeamSuccessBonus;
getActionTypeSkillSuccessBonus = operationSkillSuccessBonus;
getChaosSuccessFactor(inst: Bladeburner /*, params: ISuccessChanceParams*/): number {
const city = inst.getCurrentCity();
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
const mult = Math.pow(diff, 0.5);
return mult;
}
return 1;
}
getSuccessChance(inst: Bladeburner, person: Person, params: SuccessChanceParams) {
if (this.name == BladeOperationName.raid && inst.getCurrentCity().comms <= 0) return 0;
return ActionClass.prototype.getSuccessChance.call(this, inst, person, params);
}
reset() {
LevelableActionClass.prototype.reset.call(this);
this.teamCount = 0;
}
toJSON(): IReviverValue {
return this.save("Operation", "teamCount");
}
loadData(loadedObject: Operation): void {
this.teamCount = clampInteger(loadedObject.teamCount, 0);
LevelableActionClass.prototype.loadData.call(this, loadedObject);
}
static fromJSON(value: IReviverValue): Operation {
return Generic_fromJSON(Operation, value.data);
}
}
constructorsForReviver.Operation = Operation;
// shared member functions for Operation and BlackOperation
export const operationSkillSuccessBonus = (inst: Bladeburner) => {
return inst.getSkillMult(BladeMultName.successChanceOperation);
};
export function operationTeamSuccessBonus(this: Operation | BlackOperation, inst: Bladeburner) {
if (this.teamCount && this.teamCount > 0) {
this.teamCount = Math.min(this.teamCount, inst.teamSize);
const teamMultiplier = Math.pow(this.teamCount, 0.05);
return teamMultiplier;
}
return 1;
}

View File

@@ -0,0 +1,7 @@
// Barrel file for easier importing
export { ActionClass } from "./Action";
export { BlackOperation } from "./BlackOperation";
export { Contract } from "./Contract";
export { GeneralAction } from "./GeneralAction";
export { Operation } from "./Operation";
export { LevelableActionClass } from "./LevelableAction";

View File

@@ -1,32 +0,0 @@
import { Operation, IOperationParams } from "./Operation";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export class BlackOperation extends Operation {
constructor(params: IOperationParams | null = null) {
super(params);
this.count = 1;
}
// To be implemented by subtypes
getActionTimePenalty(): number {
return 1.5;
}
getChaosCompetencePenalty(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
return 1;
}
getChaosDifficultyBonus(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
return 1;
}
toJSON(): IReviverValue {
return Generic_toJSON("BlackOperation", this);
}
static fromJSON(value: IReviverValue): Operation {
return Generic_fromJSON(BlackOperation, value.data);
}
}
constructorsForReviver.BlackOperation = BlackOperation;

View File

@@ -1,571 +0,0 @@
import { BlackOperation } from "./BlackOperation";
import { BlackOperationName } from "@enums";
export const BlackOperations: Record<string, BlackOperation> = {};
(function () {
BlackOperations[BlackOperationName.OperationTyphoon] = new BlackOperation({
name: BlackOperationName.OperationTyphoon,
baseDifficulty: 2000,
reqdRank: 2.5e3,
rankGain: 50,
rankLoss: 10,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationZero] = new BlackOperation({
name: BlackOperationName.OperationZero,
baseDifficulty: 2500,
reqdRank: 5e3,
rankGain: 60,
rankLoss: 15,
hpLoss: 50,
weights: {
hack: 0.2,
str: 0.15,
def: 0.15,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations[BlackOperationName.OperationX] = new BlackOperation({
name: BlackOperationName.OperationX,
baseDifficulty: 3000,
reqdRank: 7.5e3,
rankGain: 75,
rankLoss: 15,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationTitan] = new BlackOperation({
name: BlackOperationName.OperationTitan,
baseDifficulty: 4000,
reqdRank: 10e3,
rankGain: 100,
rankLoss: 20,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationAres] = new BlackOperation({
name: BlackOperationName.OperationAres,
baseDifficulty: 5000,
reqdRank: 12.5e3,
rankGain: 125,
rankLoss: 20,
hpLoss: 200,
weights: {
hack: 0,
str: 0.25,
def: 0.25,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationArchangel] = new BlackOperation({
name: BlackOperationName.OperationArchangel,
baseDifficulty: 7500,
reqdRank: 15e3,
rankGain: 200,
rankLoss: 20,
hpLoss: 25,
weights: {
hack: 0,
str: 0.2,
def: 0.2,
dex: 0.3,
agi: 0.3,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationJuggernaut] = new BlackOperation({
name: BlackOperationName.OperationJuggernaut,
baseDifficulty: 10e3,
reqdRank: 20e3,
rankGain: 300,
rankLoss: 40,
hpLoss: 300,
weights: {
hack: 0,
str: 0.25,
def: 0.25,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationRedDragon] = new BlackOperation({
name: BlackOperationName.OperationRedDragon,
baseDifficulty: 12.5e3,
reqdRank: 25e3,
rankGain: 500,
rankLoss: 50,
hpLoss: 500,
weights: {
hack: 0.05,
str: 0.2,
def: 0.2,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationK] = new BlackOperation({
name: BlackOperationName.OperationK,
baseDifficulty: 15e3,
reqdRank: 30e3,
rankGain: 750,
rankLoss: 60,
hpLoss: 1000,
weights: {
hack: 0.05,
str: 0.2,
def: 0.2,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationDeckard] = new BlackOperation({
name: BlackOperationName.OperationDeckard,
baseDifficulty: 20e3,
reqdRank: 40e3,
rankGain: 1e3,
rankLoss: 75,
hpLoss: 200,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationTyrell] = new BlackOperation({
name: BlackOperationName.OperationTyrell,
baseDifficulty: 25e3,
reqdRank: 50e3,
rankGain: 1.5e3,
rankLoss: 100,
hpLoss: 500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationWallace] = new BlackOperation({
name: BlackOperationName.OperationWallace,
baseDifficulty: 30e3,
reqdRank: 75e3,
rankGain: 2e3,
rankLoss: 150,
hpLoss: 1500,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationShoulderOfOrion] = new BlackOperation({
name: BlackOperationName.OperationShoulderOfOrion,
baseDifficulty: 35e3,
reqdRank: 100e3,
rankGain: 2.5e3,
rankLoss: 500,
hpLoss: 1500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations[BlackOperationName.OperationHyron] = new BlackOperation({
name: BlackOperationName.OperationHyron,
baseDifficulty: 40e3,
reqdRank: 125e3,
rankGain: 3e3,
rankLoss: 1e3,
hpLoss: 500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationMorpheus] = new BlackOperation({
name: BlackOperationName.OperationMorpheus,
baseDifficulty: 45e3,
reqdRank: 150e3,
rankGain: 4e3,
rankLoss: 1e3,
hpLoss: 100,
weights: {
hack: 0.05,
str: 0.15,
def: 0.15,
dex: 0.3,
agi: 0.3,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations[BlackOperationName.OperationIonStorm] = new BlackOperation({
name: BlackOperationName.OperationIonStorm,
baseDifficulty: 50e3,
reqdRank: 175e3,
rankGain: 5e3,
rankLoss: 1e3,
hpLoss: 5000,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationAnnihilus] = new BlackOperation({
name: BlackOperationName.OperationAnnihilus,
baseDifficulty: 55e3,
reqdRank: 200e3,
rankGain: 7.5e3,
rankLoss: 1e3,
hpLoss: 10e3,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationUltron] = new BlackOperation({
name: BlackOperationName.OperationUltron,
baseDifficulty: 60e3,
reqdRank: 250e3,
rankGain: 10e3,
rankLoss: 2e3,
hpLoss: 10e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations[BlackOperationName.OperationCenturion] = new BlackOperation({
name: BlackOperationName.OperationCenturion,
baseDifficulty: 70e3,
reqdRank: 300e3,
rankGain: 15e3,
rankLoss: 5e3,
hpLoss: 10e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
BlackOperations[BlackOperationName.OperationVindictus] = new BlackOperation({
name: BlackOperationName.OperationVindictus,
baseDifficulty: 75e3,
reqdRank: 350e3,
rankGain: 20e3,
rankLoss: 20e3,
hpLoss: 20e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
BlackOperations[BlackOperationName.OperationDaedalus] = new BlackOperation({
name: BlackOperationName.OperationDaedalus,
baseDifficulty: 80e3,
reqdRank: 400e3,
rankGain: 40e3,
rankLoss: 10e3,
hpLoss: 100e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
})();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,161 +1,89 @@
import { CityName } from "@enums";
import { BladeburnerConstants } from "./data/Constants";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { getRandomIntInclusive } from "../utils/helpers/getRandomIntInclusive";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { addOffset } from "../utils/helpers/addOffset";
import { CityName } from "@enums";
interface IChangePopulationByCountParams {
/** How much the estimate should change by. */
estChange: number;
/** Add offset to estimate (offset by percentage). */
estOffset: number;
}
interface IChangePopulationByPercentageParams {
nonZero: boolean;
changeEstEqually: boolean;
}
import { clampInteger, clampNumber } from "../utils/helpers/clampNumber";
export class City {
/** Name of the city. */
name: CityName;
/** Population of the city. */
pop = 0;
/** Population estimation of the city. */
popEst = 0;
/** Number of communities in the city. */
comms = 0;
/** Chaos level of the city. */
pop = 0; // Population
popEst = 0; // Population estimate
comms = 0; // Number of communities
chaos = 0;
constructor(name = CityName.Sector12) {
this.name = name;
// Synthoid population and estimate
this.pop = getRandomInt(BladeburnerConstants.PopulationThreshold, 1.5 * BladeburnerConstants.PopulationThreshold);
this.pop = getRandomIntInclusive(
BladeburnerConstants.PopulationThreshold,
1.5 * BladeburnerConstants.PopulationThreshold,
);
this.popEst = this.pop * (Math.random() + 0.5);
// Number of Synthoid communities population and estimate
this.comms = getRandomInt(5, 150);
this.comms = getRandomIntInclusive(5, 150);
this.chaos = 0;
}
/** p is the percentage, not the multiplier (e.g. pass in p = 5 for 5%) */
/** @param {number} p - the percentage change, not the multiplier. e.g. pass in p = 5 for 5% */
changeChaosByPercentage(p: number): void {
if (isNaN(p)) {
throw new Error("NaN passed into City.chaosChaosByPercentage()");
}
if (p === 0) {
return;
}
this.chaos += this.chaos * (p / 100);
if (this.chaos < 0) {
this.chaos = 0;
}
this.chaos = clampNumber(this.chaos * (1 + p / 100), 0);
}
improvePopulationEstimateByCount(n: number): void {
if (isNaN(n)) {
throw new Error("NaN passed into City.improvePopulationEstimateByCount()");
}
if (this.popEst < this.pop) {
this.popEst += n;
if (this.popEst > this.pop) {
this.popEst = this.pop;
}
} else if (this.popEst > this.pop) {
this.popEst -= n;
if (this.popEst < this.pop) {
this.popEst = this.pop;
}
}
n = clampInteger(n, 0);
const diff = Math.abs(this.popEst - this.pop);
// Chgnge would overshoot actual population -> make estimate accurate
if (diff <= n) this.popEst = this.pop;
// Otherwise make enstimate closer by n
else if (this.popEst < this.pop) this.popEst += n;
else this.popEst -= n;
}
/** p is the percentage, not the multiplier (e.g. pass in p = 5 for 5%) */
/** @param {number} p - the percentage change, not the multiplier. e.g. pass in p = 5 for 5% */
improvePopulationEstimateByPercentage(p: number, skillMult = 1): void {
p = p * skillMult;
if (isNaN(p)) {
throw new Error("NaN passed into City.improvePopulationEstimateByPercentage()");
}
if (this.popEst < this.pop) {
++this.popEst; // In case estimate is 0
this.popEst *= 1 + p / 100;
if (this.popEst > this.pop) {
this.popEst = this.pop;
}
} else if (this.popEst > this.pop) {
this.popEst *= 1 - p / 100;
if (this.popEst < this.pop) {
this.popEst = this.pop;
}
}
p = clampNumber((p * skillMult) / 100);
const diff = Math.abs(this.popEst - this.pop);
// Chgnge would overshoot actual population -> make estimate accurate
if (diff <= p * this.popEst) this.popEst = this.pop;
// Otherwise make enstimate closer by n
else if (this.popEst < this.pop) this.popEst = clampNumber(this.popEst * (1 + p));
else this.popEst = clampNumber(this.popEst * (1 - p));
}
changePopulationByCount(n: number, params: IChangePopulationByCountParams = { estChange: 0, estOffset: 0 }): void {
if (isNaN(n)) {
throw new Error("NaN passed into City.changePopulationByCount()");
}
this.pop += n;
/**
* @param params.estChange - Number to change the estimate by
* @param params.estOffset - Offset percentage to apply to estimate */
changePopulationByCount(n: number, params = { estChange: 0, estOffset: 0 }): void {
n = clampInteger(n);
this.pop = clampInteger(this.pop + n, 0);
if (params.estChange && !isNaN(params.estChange)) {
this.popEst += params.estChange;
}
if (params.estOffset) {
this.popEst = addOffset(this.popEst, params.estOffset);
}
this.popEst = Math.max(this.popEst, 0);
this.popEst = clampInteger(this.popEst, 0);
}
/**
* @p is the percentage, not the multiplier. e.g. pass in p = 5 for 5%
* @params options:
* changeEstEqually(bool) - Change the population estimate by an equal amount
* nonZero (bool) - Set to true to ensure that population always changes by at least 1
*/
changePopulationByPercentage(
p: number,
params: IChangePopulationByPercentageParams = {
nonZero: false,
changeEstEqually: false,
},
): number {
if (isNaN(p)) {
throw new Error("NaN passed into City.changePopulationByPercentage()");
}
if (p === 0) {
return 0;
}
let change = Math.round(this.pop * (p / 100));
* @param {number} p - the percentage change, not the multiplier. e.g. pass in p = 5 for 5%
* @param {boolean} params.changeEstEqually - Whether to change the population estimate by an equal amount
* @param {boolean} params.nonZero - Whether to ensure that population always changes by at least 1 */
changePopulationByPercentage(p: number, params = { nonZero: false, changeEstEqually: false }): number {
let change = clampInteger(this.pop * (p / 100));
// Population always changes by at least 1
if (params.nonZero && change === 0) {
p > 0 ? (change = 1) : (change = -1);
}
if (params.nonZero && change === 0) change = p > 0 ? 1 : -1;
this.pop += change;
if (params.changeEstEqually) {
this.popEst += change;
if (this.popEst < 0) {
this.popEst = 0;
}
}
this.pop = clampInteger(this.pop + change, 0);
if (params.changeEstEqually) this.popEst = clampInteger(this.popEst + change, 0);
return change;
}
changeChaosByCount(n: number): void {
if (isNaN(n)) {
throw new Error("NaN passed into City.changeChaosByCount()");
}
if (n === 0) {
return;
}
this.chaos += n;
if (this.chaos < 0) {
this.chaos = 0;
}
this.chaos = clampNumber(this.chaos + n, 0);
}
/** Serialize the current object to a JSON save state. */

View File

@@ -1,23 +0,0 @@
import { Bladeburner } from "./Bladeburner";
import { Action, IActionParams } from "./Action";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export class Contract extends Action {
constructor(params: IActionParams | null = null) {
super(params);
}
getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
return inst.skillMultipliers.successChanceContract;
}
toJSON(): IReviverValue {
return Generic_toJSON("Contract", this);
}
static fromJSON(value: IReviverValue): Contract {
return Generic_fromJSON(Contract, value.data);
}
}
constructorsForReviver.Contract = Contract;

View File

@@ -1,4 +1,31 @@
export enum BlackOperationName {
export enum BladeActionType {
general = "General",
contract = "Contracts",
operation = "Operations",
blackOp = "Black Operations",
}
export enum BladeGeneralActionName {
training = "Training",
fieldAnalysis = "Field Analysis",
recruitment = "Recruitment",
diplomacy = "Diplomacy",
hyperbolicRegen = "Hyperbolic Regeneration Chamber",
inciteViolence = "Incite Violence",
}
export enum BladeContractName {
tracking = "Tracking",
bountyHunter = "Bounty Hunter",
retirement = "Retirement",
}
export enum BladeOperationName {
investigation = "Investigation",
undercover = "Undercover Operation",
sting = "Sting Operation",
raid = "Raid",
stealthRetirement = "Stealth Retirement Operation",
assassination = "Assassination",
}
export enum BladeBlackOpName {
OperationTyphoon = "Operation Typhoon",
OperationZero = "Operation Zero",
OperationX = "Operation X",
@@ -21,3 +48,36 @@ export enum BlackOperationName {
OperationVindictus = "Operation Vindictus",
OperationDaedalus = "Operation Daedalus",
}
export enum BladeSkillName {
bladesIntuition = "Blade's Intuition",
cloak = "Cloak",
shortCircuit = "Short-Circuit",
digitalObserver = "Digital Observer",
tracer = "Tracer",
overclock = "Overclock",
reaper = "Reaper",
evasiveSystem = "Evasive System",
datamancer = "Datamancer",
cybersEdge = "Cyber's Edge",
handsOfMidas = "Hands of Midas",
hyperdrive = "Hyperdrive",
}
export enum BladeMultName {
successChanceAll = "Total Success Chance",
successChanceStealth = "Stealth Success Chance",
successChanceKill = "Retirement Success Chance",
successChanceContract = "Contract Success Chance",
successChanceOperation = "Operation Success Chance",
successChanceEstimate = "Synthoid Data Estimate",
actionTime = "Action Time",
effStr = "Effective Strength",
effDef = "Effective Defense",
effDex = "Effective Dexterity",
effAgi = "Effective Agility",
effCha = "Effective Charisma",
stamina = "Stamina",
money = "Contract Money",
expGain = "Experience Gain",
}

View File

@@ -1,18 +0,0 @@
import { Action } from "./Action";
export const GeneralActions: Record<string, Action> = {};
const actionNames: string[] = [
"Training",
"Field Analysis",
"Recruitment",
"Diplomacy",
"Hyperbolic Regeneration Chamber",
"Incite Violence",
];
for (const actionName of actionNames) {
GeneralActions[actionName] = new Action({
name: actionName,
});
}

View File

@@ -1,56 +0,0 @@
import { Bladeburner } from "./Bladeburner";
import { BladeburnerConstants } from "./data/Constants";
import { Action, IActionParams } from "./Action";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export interface IOperationParams extends IActionParams {
reqdRank?: number;
teamCount?: number;
}
export class Operation extends Action {
reqdRank = 100;
teamCount = 0;
constructor(params: IOperationParams | null = null) {
super(params);
if (params && params.reqdRank) this.reqdRank = params.reqdRank;
if (params && params.teamCount) this.teamCount = params.teamCount;
}
// For actions that have teams. To be implemented by subtypes.
getTeamSuccessBonus(inst: Bladeburner): number {
if (this.teamCount && this.teamCount > 0) {
this.teamCount = Math.min(this.teamCount, inst.teamSize);
const teamMultiplier = Math.pow(this.teamCount, 0.05);
return teamMultiplier;
}
return 1;
}
getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
return inst.skillMultipliers.successChanceOperation;
}
getChaosDifficultyBonus(inst: Bladeburner /*, params: ISuccessChanceParams*/): number {
const city = inst.getCurrentCity();
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
const mult = Math.pow(diff, 0.5);
return mult;
}
return 1;
}
toJSON(): IReviverValue {
return Generic_toJSON("Operation", this);
}
static fromJSON(value: IReviverValue): Operation {
return Generic_fromJSON(Operation, value.data);
}
}
constructorsForReviver.Operation = Operation;

View File

@@ -1,161 +1,56 @@
import type { BladeMultName, BladeSkillName } from "@enums";
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
import { Bladeburner } from "./Bladeburner";
import { Availability } from "./Types";
import { PositiveInteger, PositiveSafeInteger, isPositiveInteger } from "../types";
import { PartialRecord, getRecordEntries } from "../Types/Record";
interface ISkillParams {
name: string;
interface SkillParams {
name: BladeSkillName;
desc: string;
baseCost?: number;
costInc?: number;
maxLvl?: number;
successChanceAll?: number;
successChanceStealth?: number;
successChanceKill?: number;
successChanceContract?: number;
successChanceOperation?: number;
successChanceEstimate?: number;
actionTime?: number;
effHack?: number;
effStr?: number;
effDef?: number;
effDex?: number;
effAgi?: number;
effCha?: number;
stamina?: number;
money?: number;
expGain?: number;
mults: PartialRecord<BladeMultName, number>;
}
export class Skill {
name: string;
name: BladeSkillName;
desc: string;
// Cost is in Skill Points
baseCost = 1;
baseCost: number;
// Additive cost increase per level
costInc = 1;
maxLvl = 0;
costInc: number;
maxLvl: number;
mults: PartialRecord<BladeMultName, number> = {};
/**
* These benefits are additive. So total multiplier will be level (handled externally) times the
* effects below
*/
successChanceAll = 0;
successChanceStealth = 0;
successChanceKill = 0;
successChanceContract = 0;
successChanceOperation = 0;
/**
* This multiplier affects everything that increases synthoid population/community estimate
* e.g. Field analysis, Investigation Op, Undercover Op
*/
successChanceEstimate = 0;
actionTime = 0;
effHack = 0;
effStr = 0;
effDef = 0;
effDex = 0;
effAgi = 0;
effCha = 0;
stamina = 0;
money = 0;
expGain = 0;
constructor(params: ISkillParams = { name: "foo", desc: "foo" }) {
if (!params.name) {
throw new Error("Failed to initialize Bladeburner Skill. No name was specified in ctor");
}
if (!params.desc) {
throw new Error("Failed to initialize Bladeburner Skills. No desc was specified in ctor");
}
constructor(params: SkillParams) {
this.name = params.name;
this.desc = params.desc;
this.baseCost = params.baseCost ? params.baseCost : 1;
this.costInc = params.costInc ? params.costInc : 1;
if (params.maxLvl) {
this.maxLvl = params.maxLvl;
}
if (params.successChanceAll) {
this.successChanceAll = params.successChanceAll;
}
if (params.successChanceStealth) {
this.successChanceStealth = params.successChanceStealth;
}
if (params.successChanceKill) {
this.successChanceKill = params.successChanceKill;
}
if (params.successChanceContract) {
this.successChanceContract = params.successChanceContract;
}
if (params.successChanceOperation) {
this.successChanceOperation = params.successChanceOperation;
}
if (params.successChanceEstimate) {
this.successChanceEstimate = params.successChanceEstimate;
}
if (params.actionTime) {
this.actionTime = params.actionTime;
}
if (params.effHack) {
this.effHack = params.effHack;
}
if (params.effStr) {
this.effStr = params.effStr;
}
if (params.effDef) {
this.effDef = params.effDef;
}
if (params.effDex) {
this.effDex = params.effDex;
}
if (params.effAgi) {
this.effAgi = params.effAgi;
}
if (params.effCha) {
this.effCha = params.effCha;
}
if (params.stamina) {
this.stamina = params.stamina;
}
if (params.money) {
this.money = params.money;
}
if (params.expGain) {
this.expGain = params.expGain;
}
this.baseCost = params.baseCost ?? 1;
this.costInc = params.costInc ?? 1;
this.maxLvl = params.maxLvl ?? Number.MAX_SAFE_INTEGER;
for (const [multName, mult] of getRecordEntries(params.mults)) this.mults[multName] = mult;
}
calculateCost(currentLevel: number, count = 1): number {
//Recursive mode does not handle invalid inputs properly, but it should never
//be possible for it to run with them. For the sake of not crashing the game,
const recursiveMode = (currentLevel: number, count: number): number => {
calculateCost(currentLevel: number, count = 1 as PositiveInteger): number {
if (currentLevel + count > this.maxLvl) return Infinity;
const recursiveMode = (currentLevel: number, count: PositiveSafeInteger): number => {
if (count <= 1) {
return Math.floor((this.baseCost + currentLevel * this.costInc) * currentNodeMults.BladeburnerSkillCost);
} else {
const thisUpgrade = Math.floor(
(this.baseCost + currentLevel * this.costInc) * currentNodeMults.BladeburnerSkillCost,
);
return this.calculateCost(currentLevel + 1, count - 1) + thisUpgrade;
return this.calculateCost(currentLevel + 1, (count - 1) as PositiveSafeInteger) + thisUpgrade;
}
};
//Count must be a positive integer.
if (count < 0 || count % 1 != 0) {
throw new Error(`${count} is an invalid number of upgrades`);
}
//Use recursive mode if count is small
if (count <= 100) {
return recursiveMode(currentLevel, count);
}
//Use optimized mode if count is large
// Use recursive mode if count is small
if (count <= 100) return recursiveMode(currentLevel, count as PositiveSafeInteger);
// Use optimized mode if count is large
else {
//unFloored is roughly equivalent to
//(this.baseCost + currentLevel * this.costInc) * BitNodeMultipliers.BladeburnerSkillCost
@@ -166,26 +61,16 @@ export class Skill {
}
}
getMultiplier(name: string): number {
if (name === "successChanceAll") return this.successChanceAll;
if (name === "successChanceStealth") return this.successChanceStealth;
if (name === "successChanceKill") return this.successChanceKill;
if (name === "successChanceContract") return this.successChanceContract;
if (name === "successChanceOperation") return this.successChanceOperation;
if (name === "successChanceEstimate") return this.successChanceEstimate;
canUpgrade(bladeburner: Bladeburner, count = 1): Availability<{ cost: number }> {
const currentLevel = bladeburner.skills[this.name] ?? 0;
if (!isPositiveInteger(count)) return { error: `Invalid upgrade count ${count}` };
if (currentLevel + count > this.maxLvl) return { error: `Upgraded level ${currentLevel + count} exceeds max` };
const cost = this.calculateCost(currentLevel, count);
if (cost > bladeburner.skillPoints) return { error: `Insufficient skill points for upgrade` };
return { available: true, cost };
}
if (name === "actionTime") return this.actionTime;
if (name === "effHack") return this.effHack;
if (name === "effStr") return this.effStr;
if (name === "effDef") return this.effDef;
if (name === "effDex") return this.effDex;
if (name === "effAgi") return this.effAgi;
if (name === "effCha") return this.effCha;
if (name === "stamina") return this.stamina;
if (name === "money") return this.money;
if (name === "expGain") return this.expGain;
return 0;
getMultiplier(name: BladeMultName): number {
return this.mults[name] ?? 0;
}
}

31
src/Bladeburner/Types.ts Normal file
View File

@@ -0,0 +1,31 @@
import type { BlackOperation, Contract, GeneralAction, Operation } from "./Actions";
import type {
BladeActionType,
BladeBlackOpName,
BladeContractName,
BladeOperationName,
BladeGeneralActionName,
} from "@enums";
export interface SuccessChanceParams {
/** Whether the success chance should be based on estimated statistics */
est: boolean;
}
type AvailabilitySuccess<T extends object> = { available: true } & T;
type AvailabilityFailure = { available?: undefined; error: string };
export type Availability<T extends object = object> = AvailabilitySuccess<T> | AvailabilityFailure;
type AttemptSuccess<T extends object> = { success: true; message?: string } & T;
type AttemptFailure = { success?: undefined; message: string };
export type Attempt<T extends object = object> = AttemptSuccess<T> | AttemptFailure;
export type Action = Contract | Operation | BlackOperation | GeneralAction;
export type ActionIdentifier =
| { type: BladeActionType.blackOp; name: BladeBlackOpName }
| { type: BladeActionType.contract; name: BladeContractName }
| { type: BladeActionType.operation; name: BladeOperationName }
| { type: BladeActionType.general; name: BladeGeneralActionName };
export type LevelableAction = Contract | Operation;

View File

@@ -1,29 +0,0 @@
// Action Identifier enum
export const ActionTypes: {
[key: string]: number;
Idle: number;
Contract: number;
Operation: number;
BlackOp: number;
BlackOperation: number;
Training: number;
Recruitment: number;
FieldAnalysis: number;
"Field Analysis": number;
Diplomacy: number;
"Hyperbolic Regeneration Chamber": number;
"Incite Violence": number;
} = {
Idle: 1,
Contract: 2,
Operation: 3,
BlackOp: 4,
BlackOperation: 4,
Training: 5,
Recruitment: 6,
FieldAnalysis: 7,
"Field Analysis": 7,
Diplomacy: 8,
"Hyperbolic Regeneration Chamber": 9,
"Incite Violence": 10,
};

View File

@@ -0,0 +1,737 @@
import { BlackOperation } from "../Actions/BlackOperation";
import { BladeBlackOpName, CityName, FactionName } from "@enums";
export const BlackOperations: Record<BladeBlackOpName, BlackOperation> = {
[BladeBlackOpName.OperationTyphoon]: new BlackOperation({
name: BladeBlackOpName.OperationTyphoon,
n: 0,
baseDifficulty: 2000,
reqdRank: 2.5e3,
rankGain: 50,
rankLoss: 10,
hpLoss: 100,
weights: {
hacking: 0.1,
strength: 0.2,
defense: 0.2,
dexterity: 0.2,
agility: 0.2,
charisma: 0,
intelligence: 0.1,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
"Obadiah Zenyatta is the leader of a RedWater PMC. It has long been known among the intelligence community " +
"that Zenyatta, along with the rest of the PMC, is a Synthoid.\n\n" +
`The goal of ${BladeBlackOpName.OperationTyphoon} is to find and eliminate Zenyatta and RedWater by any means ` +
"necessary. After the task is completed, the actions must be covered up from the general public.",
}),
[BladeBlackOpName.OperationZero]: new BlackOperation({
name: BladeBlackOpName.OperationZero,
n: 1,
baseDifficulty: 2500,
reqdRank: 5e3,
rankGain: 60,
rankLoss: 15,
hpLoss: 50,
weights: {
hacking: 0.2,
strength: 0.15,
defense: 0.15,
dexterity: 0.2,
agility: 0.2,
charisma: 0,
intelligence: 0.1,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isStealth: true,
desc:
"AeroCorp is one of the world's largest defense contractors. Its leader, Steve Watataki, is thought to be " +
"a supporter of Synthoid rights. He must be removed.\n\n" +
`The goal of ${BladeBlackOpName.OperationZero} is to covertly infiltrate AeroCorp and uncover any incriminating ` +
"evidence or information against Watataki that will cause him to be removed from his position at AeroCorp. " +
"Incriminating evidence can be fabricated as a last resort. Be warned that AeroCorp has some of the most advanced " +
"security measures in the world.",
}),
[BladeBlackOpName.OperationX]: new BlackOperation({
name: BladeBlackOpName.OperationX,
n: 2,
baseDifficulty: 3000,
reqdRank: 7.5e3,
rankGain: 75,
rankLoss: 15,
hpLoss: 100,
weights: {
hacking: 0.1,
strength: 0.2,
defense: 0.2,
dexterity: 0.2,
agility: 0.2,
charisma: 0,
intelligence: 0.1,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
"We have recently discovered an underground publication group called Samizdat. Even though most of their " +
"publications are nonsensical conspiracy theories, the average human is gullible enough to believe them. Many of " +
"their works discuss Synthoids and pose a threat to society. The publications are spreading rapidly in China and " +
"other Eastern countries.\n\n" +
"Samizdat has done a good job of keeping hidden and anonymous. However, we've just received intelligence that " +
`their base of operations is in ${CityName.Ishima}'s underground sewer systems. Your task is to investigate the ` +
"sewer systems, and eliminate Samizdat. They must never publish anything again.",
}),
[BladeBlackOpName.OperationTitan]: new BlackOperation({
name: BladeBlackOpName.OperationTitan,
n: 3,
baseDifficulty: 4000,
reqdRank: 10e3,
rankGain: 100,
rankLoss: 20,
hpLoss: 100,
weights: {
hacking: 0.1,
strength: 0.2,
defense: 0.2,
dexterity: 0.2,
agility: 0.2,
charisma: 0,
intelligence: 0.1,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
"Several months ago Titan Laboratories' Bioengineering department was infiltrated by Synthoids. As far as we " +
"know, Titan Laboratories' management has no knowledge about this. We don't know what the Synthoids are up to, " +
"but the research that they could be conducting using Titan Laboratories' vast resources is potentially very " +
"dangerous.\n\n" +
`Your goal is to enter and destroy the Bioengineering department's facility in ${CityName.Aevum}. The task is not ` +
"just to retire the Synthoids there, but also to destroy any information or research at the facility that is " +
"relevant to the Synthoids and their goals.",
}),
[BladeBlackOpName.OperationAres]: new BlackOperation({
name: BladeBlackOpName.OperationAres,
n: 4,
baseDifficulty: 5000,
reqdRank: 12.5e3,
rankGain: 125,
rankLoss: 20,
hpLoss: 200,
weights: {
hacking: 0,
strength: 0.25,
defense: 0.25,
dexterity: 0.25,
agility: 0.25,
charisma: 0,
intelligence: 0,
},
decays: {
hacking: 0,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
"One of our undercover agents, Agent Carter, has informed us of a massive weapons deal going down in Dubai " +
"between rogue Russian militants and a radical Synthoid community. These weapons are next-gen plasma and energy " +
"weapons. It is critical for the safety of humanity that this deal does not happen.\n\n" +
"Your task is to intercept the deal. Leave no survivors.",
}),
[BladeBlackOpName.OperationArchangel]: new BlackOperation({
name: BladeBlackOpName.OperationArchangel,
n: 5,
baseDifficulty: 7500,
reqdRank: 15e3,
rankGain: 200,
rankLoss: 20,
hpLoss: 25,
weights: {
hacking: 0,
strength: 0.2,
defense: 0.2,
dexterity: 0.3,
agility: 0.3,
charisma: 0,
intelligence: 0,
},
decays: {
hacking: 0,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
"Our analysts have discovered that the popular Red Rabbit brothel in Amsterdam is run and 'staffed' by MK-VI " +
"Synthoids. Intelligence suggests that the profit from this brothel is used to fund a large black market arms " +
"trafficking operation.\n\n" +
"The goal of this operation is to take out the leaders that are running the Red Rabbit brothel. Try to limit the " +
"number of other casualties, but do what you must to complete the mission.",
}),
[BladeBlackOpName.OperationJuggernaut]: new BlackOperation({
name: BladeBlackOpName.OperationJuggernaut,
n: 6,
baseDifficulty: 10e3,
reqdRank: 20e3,
rankGain: 300,
rankLoss: 40,
hpLoss: 300,
weights: {
hacking: 0,
strength: 0.25,
defense: 0.25,
dexterity: 0.25,
agility: 0.25,
charisma: 0,
intelligence: 0,
},
decays: {
hacking: 0,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
"The CIA has just encountered a new security threat. A new criminal group, lead by a shadowy operative who calls " +
"himself Juggernaut, has been smuggling drugs and weapons (including suspected bioweapons) into " +
`${CityName.Sector12}. We also have reason to believe they tried to break into one of Universal Energy's ` +
"facilities in order to cause a city-wide blackout. The CIA suspects that Juggernaut is a heavily-augmented " +
"Synthoid, and have thus enlisted our help.\n\n" +
"Your mission is to eradicate Juggernaut and his followers.",
}),
[BladeBlackOpName.OperationRedDragon]: new BlackOperation({
name: BladeBlackOpName.OperationRedDragon,
n: 7,
baseDifficulty: 12.5e3,
reqdRank: 25e3,
rankGain: 500,
rankLoss: 50,
hpLoss: 500,
weights: {
hacking: 0.05,
strength: 0.2,
defense: 0.2,
dexterity: 0.25,
agility: 0.25,
charisma: 0,
intelligence: 0.05,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
`The ${FactionName.Tetrads} criminal organization is suspected of reverse-engineering the MK-VI Synthoid design. ` +
"We believe they altered and possibly improved the design and began manufacturing their own Synthoid models in" +
"order to bolster their criminal activities.\n\n" +
`Your task is to infiltrate and destroy the ${FactionName.Tetrads}' base of operations in Los Angeles. ` +
"Intelligence tells us that their base houses one of their Synthoid manufacturing units.",
}),
[BladeBlackOpName.OperationK]: new BlackOperation({
name: BladeBlackOpName.OperationK,
n: 8,
baseDifficulty: 15e3,
reqdRank: 30e3,
rankGain: 750,
rankLoss: 60,
hpLoss: 1000,
weights: {
hacking: 0.05,
strength: 0.2,
defense: 0.2,
dexterity: 0.25,
agility: 0.25,
charisma: 0,
intelligence: 0.05,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
"CODE RED SITUATION. Our intelligence tells us that VitaLife has discovered a new android cloning technology. " +
"This technology is supposedly capable of cloning Synthoids, not only physically but also their advanced AI " +
"modules. We do not believe that VitaLife is trying to use this technology illegally or maliciously, but if any " +
"Synthoids were able to infiltrate the corporation and take advantage of this technology then the results would " +
"be catastrophic.\n\n" +
"We do not have the power or jurisdiction to shut this down through legal or political means, so we must resort " +
"to a covert operation. Your goal is to destroy this technology and eliminate anyone who was involved in its " +
"creation.",
}),
[BladeBlackOpName.OperationDeckard]: new BlackOperation({
name: BladeBlackOpName.OperationDeckard,
n: 9,
baseDifficulty: 20e3,
reqdRank: 40e3,
rankGain: 1e3,
rankLoss: 75,
hpLoss: 200,
weights: {
hacking: 0,
strength: 0.24,
defense: 0.24,
dexterity: 0.24,
agility: 0.24,
charisma: 0,
intelligence: 0.04,
},
decays: {
hacking: 0,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
"Despite your success in eliminating VitaLife's new android-replicating technology in " +
`${BladeBlackOpName.OperationK}, we've discovered that a small group of MK-VI Synthoids were able to make off with ` +
"the schematics and design of the technology before the Operation. It is almost a certainty that these Synthoids " +
"are some of the rogue MK-VI ones from the Synthoid Uprising.\n\n" +
`The goal of ${BladeBlackOpName.OperationDeckard} is to hunt down these Synthoids and retire them. I don't need to ` +
"tell you how critical this mission is.",
}),
[BladeBlackOpName.OperationTyrell]: new BlackOperation({
name: BladeBlackOpName.OperationTyrell,
n: 10,
baseDifficulty: 25e3,
reqdRank: 50e3,
rankGain: 1.5e3,
rankLoss: 100,
hpLoss: 500,
weights: {
hacking: 0.1,
strength: 0.2,
defense: 0.2,
dexterity: 0.2,
agility: 0.2,
charisma: 0,
intelligence: 0.1,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
`A week ago ${FactionName.BladeIndustries} reported a small break-in at one of their ${CityName.Aevum} ` +
`Augmentation storage facilities. We figured out that ${FactionName.TheDarkArmy} was behind the heist, and didn't think ` +
"any more of it. However, we've just discovered that several known MK-VI Synthoids were part of that break-in group.\n\n" +
"We cannot have Synthoids upgrading their already-enhanced abilities with Augmentations. Your task is to hunt " +
`down associated ${FactionName.TheDarkArmy} members and eliminate them.`,
}),
[BladeBlackOpName.OperationWallace]: new BlackOperation({
name: BladeBlackOpName.OperationWallace,
n: 11,
baseDifficulty: 30e3,
reqdRank: 75e3,
rankGain: 2e3,
rankLoss: 150,
hpLoss: 1500,
weights: {
hacking: 0,
strength: 0.24,
defense: 0.24,
dexterity: 0.24,
agility: 0.24,
charisma: 0,
intelligence: 0.04,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
`Based on information gathered from ${BladeBlackOpName.OperationTyrell}, we've discovered that ` +
`${FactionName.TheDarkArmy} was well aware that there were Synthoids amongst their ranks. Even worse, we believe ` +
`that ${FactionName.TheDarkArmy} is working together with other criminal organizations such as ` +
`${FactionName.TheSyndicate} and that they are planning some sort of large-scale takeover of multiple major ` +
`cities, most notably ${CityName.Aevum}. We suspect that Synthoids have infiltrated the ranks of these criminal ` +
"factions and are trying to stage another Synthoid uprising.\n\n" +
"The best way to deal with this is to prevent it before it even happens. The goal of " +
`${BladeBlackOpName.OperationWallace} is to destroy ${FactionName.TheDarkArmy} and Syndicate factions in ` +
`${CityName.Aevum} immediately. Leave no survivors.`,
}),
[BladeBlackOpName.OperationShoulderOfOrion]: new BlackOperation({
name: BladeBlackOpName.OperationShoulderOfOrion,
n: 12,
baseDifficulty: 35e3,
reqdRank: 100e3,
rankGain: 2.5e3,
rankLoss: 500,
hpLoss: 1500,
weights: {
hacking: 0.1,
strength: 0.2,
defense: 0.2,
dexterity: 0.2,
agility: 0.2,
charisma: 0,
intelligence: 0.1,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isStealth: true,
desc:
"China's Solaris Space Systems is secretly launching the first manned spacecraft in over a decade using " +
"Synthoids. We believe China is trying to establish the first off-world colonies.\n\n" +
"The mission is to prevent this launch without instigating an international conflict. When you accept this " +
"mission you will be officially disavowed by the NSA and the national government until after you successfully " +
"return. In the event of failure, all of the operation's team members must not let themselves be captured alive.",
}),
[BladeBlackOpName.OperationHyron]: new BlackOperation({
name: BladeBlackOpName.OperationHyron,
n: 13,
baseDifficulty: 40e3,
reqdRank: 125e3,
rankGain: 3e3,
rankLoss: 1e3,
hpLoss: 500,
weights: {
hacking: 0.1,
strength: 0.2,
defense: 0.2,
dexterity: 0.2,
agility: 0.2,
charisma: 0,
intelligence: 0.1,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
`Our intelligence tells us that ${FactionName.FulcrumSecretTechnologies} is developing a quantum supercomputer ` +
"using human brains as core processors. This supercomputer is rumored to be able to store vast amounts of data " +
"and perform computations unmatched by any other supercomputer on the planet. But more importantly, the use of " +
"organic human brains means that the supercomputer may be able to reason abstractly and become self-aware.\n\n" +
"I do not need to remind you why sentient-level AIs pose a serious threat to all of mankind.\n\n" +
`The research for this project is being conducted at one of ${FactionName.FulcrumSecretTechnologies} secret ` +
`facilities in ${CityName.Aevum}, codenamed 'Alpha Ranch'. Infiltrate the compound, delete and destroy the work, ` +
"and then find and kill the project lead.",
}),
[BladeBlackOpName.OperationMorpheus]: new BlackOperation({
name: BladeBlackOpName.OperationMorpheus,
n: 14,
baseDifficulty: 45e3,
reqdRank: 150e3,
rankGain: 4e3,
rankLoss: 1e3,
hpLoss: 100,
weights: {
hacking: 0.05,
strength: 0.15,
defense: 0.15,
dexterity: 0.3,
agility: 0.3,
charisma: 0,
intelligence: 0.05,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isStealth: true,
desc:
"DreamSense Technologies is an advertising company that uses special technology to transmit their ads into the " +
"people's dreams and subconscious. They do this using broadcast transmitter towers. Based on information from our " +
`agents and informants in ${CityName.Chongqing}, we have reason to believe that one of the broadcast towers there ` +
"has been compromised by Synthoids and is being used to spread pro-Synthoid propaganda.\n\n" +
"The mission is to destroy this broadcast tower. Speed and stealth are of the utmost importance for this.",
}),
[BladeBlackOpName.OperationIonStorm]: new BlackOperation({
name: BladeBlackOpName.OperationIonStorm,
n: 15,
baseDifficulty: 50e3,
reqdRank: 175e3,
rankGain: 5e3,
rankLoss: 1e3,
hpLoss: 5000,
weights: {
hacking: 0,
strength: 0.24,
defense: 0.24,
dexterity: 0.24,
agility: 0.24,
charisma: 0,
intelligence: 0.04,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
"Our analysts have uncovered a gathering of MK-VI Synthoids that have taken up residence in the " +
`${CityName.Sector12} Slums. We don't know if they are rogue Synthoids from the Uprising, but we do know that they ` +
"have been stockpiling weapons, money, and other resources. This makes them dangerous.\n\n" +
`This is a full-scale assault operation to find and retire all of these Synthoids in the ${CityName.Sector12} ` +
"Slums.",
}),
[BladeBlackOpName.OperationAnnihilus]: new BlackOperation({
name: BladeBlackOpName.OperationAnnihilus,
n: 16,
baseDifficulty: 55e3,
reqdRank: 200e3,
rankGain: 7.5e3,
rankLoss: 1e3,
hpLoss: 10e3,
weights: {
hacking: 0,
strength: 0.24,
defense: 0.24,
dexterity: 0.24,
agility: 0.24,
charisma: 0,
intelligence: 0.04,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
"Our superiors have ordered us to eradicate everything and everyone in an underground facility located in " +
`${CityName.Aevum}. They tell us that the facility houses many dangerous Synthoids and belongs to a terrorist ` +
`organization called '${FactionName.TheCovenant}'. We have no prior intelligence about this organization, so you ` +
"are going in blind.",
}),
[BladeBlackOpName.OperationUltron]: new BlackOperation({
name: BladeBlackOpName.OperationUltron,
n: 17,
baseDifficulty: 60e3,
reqdRank: 250e3,
rankGain: 10e3,
rankLoss: 2e3,
hpLoss: 10e3,
weights: {
hacking: 0.1,
strength: 0.2,
defense: 0.2,
dexterity: 0.2,
agility: 0.2,
charisma: 0,
intelligence: 0.1,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
isKill: true,
desc:
`${FactionName.OmniTekIncorporated}, the original designer and manufacturer of Synthoids, has notified us of a ` +
"malfunction in their AI design. This malfunction, when triggered, causes MK-VI Synthoids to become radicalized " +
"and seek out the destruction of humanity. They say that this bug affects all MK-VI Synthoids, not just the rogue " +
"ones from the Uprising.\n\n" +
`${FactionName.OmniTekIncorporated} has also told us they believe someone has triggered this malfunction in a ` +
"large group of MK-VI Synthoids, and that these newly-radicalized Synthoids are now amassing in " +
`${CityName.Volhaven} to form a terrorist group called Ultron.\n\n` +
"Intelligence suggests Ultron is heavily armed and that their members are augmented. We believe Ultron is making " +
"moves to take control of and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).\n\n" +
"Your task is to find and destroy Ultron.",
}),
[BladeBlackOpName.OperationCenturion]: new BlackOperation({
name: BladeBlackOpName.OperationCenturion,
n: 18,
baseDifficulty: 70e3,
reqdRank: 300e3,
rankGain: 15e3,
rankLoss: 5e3,
hpLoss: 10e3,
weights: {
hacking: 0.1,
strength: 0.2,
defense: 0.2,
dexterity: 0.2,
agility: 0.2,
charisma: 0,
intelligence: 0.1,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
desc:
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)\n\n" +
"Throughout all of humanity's history, we have relied on technology to survive, conquer, and progress. Its " +
"advancement became our primary goal. And at the peak of human civilization technology turned into power. Global, " +
"absolute power.\n\n" +
"It seems that the universe is not without a sense of irony.\n\n" +
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",
}),
[BladeBlackOpName.OperationVindictus]: new BlackOperation({
name: BladeBlackOpName.OperationVindictus,
n: 19,
baseDifficulty: 75e3,
reqdRank: 350e3,
rankGain: 20e3,
rankLoss: 20e3,
hpLoss: 20e3,
weights: {
hacking: 0.1,
strength: 0.2,
defense: 0.2,
dexterity: 0.2,
agility: 0.2,
charisma: 0,
intelligence: 0.1,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
desc:
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)\n\n" +
"The bits are all around us. The daemons that hold the Node together can manifest themselves in many different " +
"ways.\n\n" +
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",
}),
[BladeBlackOpName.OperationDaedalus]: new BlackOperation({
name: BladeBlackOpName.OperationDaedalus,
n: 20,
baseDifficulty: 80e3,
reqdRank: 400e3,
rankGain: 40e3,
rankLoss: 10e3,
hpLoss: 100e3,
weights: {
hacking: 0.1,
strength: 0.2,
defense: 0.2,
dexterity: 0.2,
agility: 0.2,
charisma: 0,
intelligence: 0.1,
},
decays: {
hacking: 0.6,
strength: 0.8,
defense: 0.8,
dexterity: 0.8,
agility: 0.8,
charisma: 0,
intelligence: 0.75,
},
desc: "Yesterday we obeyed kings and bent our neck to emperors. Today we kneel only to truth.",
}),
};
/** Array for quick lookup by blackop number */
export const blackOpsArray = Object.values(BlackOperations).sort((a, b) => (a.n < b.n ? -1 : 1));
// Verify that all "n" properties match the index in the array
if (!blackOpsArray.every((blackOp, i) => blackOp.n === i)) {
throw new Error("blackOpsArray did not initialize with correct indices");
}

View File

@@ -1,302 +0,0 @@
import React from "react";
import { BlackOperationName, CityName, FactionName } from "@enums";
interface IBlackOp {
desc: JSX.Element;
}
export const BlackOperations: Record<string, IBlackOp | undefined> = {
[BlackOperationName.OperationTyphoon]: {
desc: (
<>
Obadiah Zenyatta is the leader of a RedWater PMC. It has long been known among the intelligence community that
Zenyatta, along with the rest of the PMC, is a Synthoid.
<br />
<br />
The goal of {BlackOperationName.OperationTyphoon} is to find and eliminate Zenyatta and RedWater by any means
necessary. After the task is completed, the actions must be covered up from the general public.
</>
),
},
[BlackOperationName.OperationZero]: {
desc: (
<>
AeroCorp is one of the world's largest defense contractors. Its leader, Steve Watataki, is thought to be a
supporter of Synthoid rights. He must be removed.
<br />
<br />
The goal of {BlackOperationName.OperationZero} is to covertly infiltrate AeroCorp and uncover any incriminating
evidence or information against Watataki that will cause him to be removed from his position at AeroCorp.
Incriminating evidence can be fabricated as a last resort. Be warned that AeroCorp has some of the most advanced
security measures in the world.
</>
),
},
[BlackOperationName.OperationX]: {
desc: (
<>
We have recently discovered an underground publication group called Samizdat. Even though most of their
publications are nonsensical conspiracy theories, the average human is gullible enough to believe them. Many of
their works discuss Synthoids and pose a threat to society. The publications are spreading rapidly in China and
other Eastern countries.
<br />
<br />
Samizdat has done a good job of keeping hidden and anonymous. However, we've just received intelligence that
their base of operations is in {CityName.Ishima}'s underground sewer systems. Your task is to investigate the
sewer systems, and eliminate Samizdat. They must never publish anything again.
</>
),
},
[BlackOperationName.OperationTitan]: {
desc: (
<>
Several months ago Titan Laboratories' Bioengineering department was infiltrated by Synthoids. As far as we
know, Titan Laboratories' management has no knowledge about this. We don't know what the Synthoids are up to,
but the research that they could be conducting using Titan Laboratories' vast resources is potentially very
dangerous.
<br />
<br />
Your goal is to enter and destroy the Bioengineering department's facility in {CityName.Aevum}. The task is not
just to retire the Synthoids there, but also to destroy any information or research at the facility that is
relevant to the Synthoids and their goals.
</>
),
},
[BlackOperationName.OperationAres]: {
desc: (
<>
One of our undercover agents, Agent Carter, has informed us of a massive weapons deal going down in Dubai
between rogue Russian militants and a radical Synthoid community. These weapons are next-gen plasma and energy
weapons. It is critical for the safety of humanity that this deal does not happen.
<br />
<br />
Your task is to intercept the deal. Leave no survivors.
</>
),
},
[BlackOperationName.OperationArchangel]: {
desc: (
<>
Our analysts have discovered that the popular Red Rabbit brothel in Amsterdam is run and 'staffed' by MK-VI
Synthoids. Intelligence suggests that the profit from this brothel is used to fund a large black market arms
trafficking operation.
<br />
<br />
The goal of this operation is to take out the leaders that are running the Red Rabbit brothel. Try to limit the
number of other casualties, but do what you must to complete the mission.
</>
),
},
[BlackOperationName.OperationJuggernaut]: {
desc: (
<>
The CIA has just encountered a new security threat. A new criminal group, lead by a shadowy operative who calls
himself Juggernaut, has been smuggling drugs and weapons (including suspected bioweapons) into{" "}
{CityName.Sector12}. We also have reason to believe they tried to break into one of Universal Energy's
facilities in order to cause a city-wide blackout. The CIA suspects that Juggernaut is a heavily-augmented
Synthoid, and have thus enlisted our help.
<br />
<br />
Your mission is to eradicate Juggernaut and his followers.
</>
),
},
[BlackOperationName.OperationRedDragon]: {
desc: (
<>
The {FactionName.Tetrads} criminal organization is suspected of reverse-engineering the MK-VI Synthoid design.
We believe they altered and possibly improved the design and began manufacturing their own Synthoid models in
order to bolster their criminal activities.
<br />
<br />
Your task is to infiltrate and destroy the {FactionName.Tetrads}' base of operations in Los Angeles.
Intelligence tells us that their base houses one of their Synthoid manufacturing units.
</>
),
},
[BlackOperationName.OperationK]: {
desc: (
<>
CODE RED SITUATION. Our intelligence tells us that VitaLife has discovered a new android cloning technology.
This technology is supposedly capable of cloning Synthoids, not only physically but also their advanced AI
modules. We do not believe that VitaLife is trying to use this technology illegally or maliciously, but if any
Synthoids were able to infiltrate the corporation and take advantage of this technology then the results would
be catastrophic.
<br />
<br />
We do not have the power or jurisdiction to shut this down through legal or political means, so we must resort
to a covert operation. Your goal is to destroy this technology and eliminate anyone who was involved in its
creation.
</>
),
},
[BlackOperationName.OperationDeckard]: {
desc: (
<>
Despite your success in eliminating VitaLife's new android-replicating technology in{" "}
{BlackOperationName.OperationK}, we've discovered that a small group of MK-VI Synthoids were able to make off
with the schematics and design of the technology before the Operation. It is almost a certainty that these
Synthoids are some of the rogue MK-VI ones from the Synthoid Uprising.
<br />
<br />
The goal of {BlackOperationName.OperationDeckard} is to hunt down these Synthoids and retire them. I don't need
to tell you how critical this mission is.
</>
),
},
[BlackOperationName.OperationTyrell]: {
desc: (
<>
A week ago {FactionName.BladeIndustries} reported a small break-in at one of their {CityName.Aevum} Augmentation
storage facilities. We figured out that {FactionName.TheDarkArmy} was behind the heist, and didn't think any
more of it. However, we've just discovered that several known MK-VI Synthoids were part of that break-in group.
<br />
<br />
We cannot have Synthoids upgrading their already-enhanced abilities with Augmentations. Your task is to hunt
down associated {FactionName.TheDarkArmy} members and eliminate them.
</>
),
},
[BlackOperationName.OperationWallace]: {
desc: (
<>
Based on information gathered from {BlackOperationName.OperationTyrell}, we've discovered that{" "}
{FactionName.TheDarkArmy} was well aware that there were Synthoids amongst their ranks. Even worse, we believe
that {FactionName.TheDarkArmy} is working together with other criminal organizations such as{" "}
{FactionName.TheSyndicate} and that they are planning some sort of large-scale takeover of multiple major
cities, most notably {CityName.Aevum}. We suspect that Synthoids have infiltrated the ranks of these criminal
factions and are trying to stage another Synthoid uprising.
<br />
<br />
The best way to deal with this is to prevent it before it even happens. The goal of{" "}
{BlackOperationName.OperationWallace} is to destroy {FactionName.TheDarkArmy} and Syndicate factions in{" "}
{CityName.Aevum} immediately. Leave no survivors.
</>
),
},
[BlackOperationName.OperationShoulderOfOrion]: {
desc: (
<>
China's Solaris Space Systems is secretly launching the first manned spacecraft in over a decade using
Synthoids. We believe China is trying to establish the first off-world colonies.
<br />
<br />
The mission is to prevent this launch without instigating an international conflict. When you accept this
mission you will be officially disavowed by the NSA and the national government until after you successfully
return. In the event of failure, all of the operation's team members must not let themselves be captured alive.
</>
),
},
[BlackOperationName.OperationHyron]: {
desc: (
<>
Our intelligence tells us that {FactionName.FulcrumSecretTechnologies} is developing a quantum supercomputer
using human brains as core processors. This supercomputer is rumored to be able to store vast amounts of data
and perform computations unmatched by any other supercomputer on the planet. But more importantly, the use of
organic human brains means that the supercomputer may be able to reason abstractly and become self-aware.
<br />
<br />
I do not need to remind you why sentient-level AIs pose a serious threat to all of mankind.
<br />
<br />
The research for this project is being conducted at one of {FactionName.FulcrumSecretTechnologies} secret
facilities in {CityName.Aevum}, codenamed 'Alpha Ranch'. Infiltrate the compound, delete and destroy the work,
and then find and kill the project lead.
</>
),
},
[BlackOperationName.OperationMorpheus]: {
desc: (
<>
DreamSense Technologies is an advertising company that uses special technology to transmit their ads into the
people's dreams and subconscious. They do this using broadcast transmitter towers. Based on information from our
agents and informants in {CityName.Chongqing}, we have reason to believe that one of the broadcast towers there
has been compromised by Synthoids and is being used to spread pro-Synthoid propaganda.
<br />
<br />
The mission is to destroy this broadcast tower. Speed and stealth are of the utmost importance for this.
</>
),
},
[BlackOperationName.OperationIonStorm]: {
desc: (
<>
Our analysts have uncovered a gathering of MK-VI Synthoids that have taken up residence in the{" "}
{CityName.Sector12} Slums. We don't know if they are rogue Synthoids from the Uprising, but we do know that they
have been stockpiling weapons, money, and other resources. This makes them dangerous.
<br />
<br />
This is a full-scale assault operation to find and retire all of these Synthoids in the {CityName.Sector12}{" "}
Slums.
</>
),
},
[BlackOperationName.OperationAnnihilus]: {
desc: (
<>
Our superiors have ordered us to eradicate everything and everyone in an underground facility located in{" "}
{CityName.Aevum}. They tell us that the facility houses many dangerous Synthoids and belongs to a terrorist
organization called '{FactionName.TheCovenant}'. We have no prior intelligence about this organization, so you
are going in blind.
</>
),
},
[BlackOperationName.OperationUltron]: {
desc: (
<>
{FactionName.OmniTekIncorporated}, the original designer and manufacturer of Synthoids, has notified us of a
malfunction in their AI design. This malfunction, when triggered, causes MK-VI Synthoids to become radicalized
and seek out the destruction of humanity. They say that this bug affects all MK-VI Synthoids, not just the rogue
ones from the Uprising.
<br />
<br />
{FactionName.OmniTekIncorporated} has also told us they believe someone has triggered this malfunction in a
large group of MK-VI Synthoids, and that these newly-radicalized Synthoids are now amassing in{" "}
{CityName.Volhaven} to form a terrorist group called Ultron.
<br />
<br />
Intelligence suggests Ultron is heavily armed and that their members are augmented. We believe Ultron is making
moves to take control of and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).
<br />
<br />
Your task is to find and destroy Ultron.
</>
),
},
[BlackOperationName.OperationCenturion]: {
desc: (
<>
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
<br />
<br />
Throughout all of humanity's history, we have relied on technology to survive, conquer, and progress. Its
advancement became our primary goal. And at the peak of human civilization technology turned into power. Global,
absolute power.
<br />
<br />
It seems that the universe is not without a sense of irony.
<br />
<br />
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
</>
),
},
[BlackOperationName.OperationVindictus]: {
desc: (
<>
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
<br />
<br />
The bits are all around us. The daemons that hold the Node together can manifest themselves in many different
ways.
<br />
<br />
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
</>
),
},
[BlackOperationName.OperationDaedalus]: {
desc: <> Yesterday we obeyed kings and bent our neck to emperors. Today we kneel only to truth.</>,
},
};

View File

@@ -0,0 +1,121 @@
import { BladeContractName } from "@enums";
import { Contract } from "../Actions/Contract";
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
import { assertLoadingType } from "../../utils/TypeAssertion";
export function createContracts(): Record<BladeContractName, Contract> {
return {
[BladeContractName.tracking]: new Contract({
name: BladeContractName.tracking,
desc:
"Identify and locate Synthoids. This contract involves reconnaissance and information-gathering ONLY. Do NOT " +
"engage. Stealth is of the utmost importance.\n\n" +
"Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for whatever " +
"city you are currently in.",
baseDifficulty: 125,
difficultyFac: 1.02,
rewardFac: 1.041,
rankGain: 0.3,
hpLoss: 0.5,
weights: {
hacking: 0,
strength: 0.05,
defense: 0.05,
dexterity: 0.35,
agility: 0.35,
charisma: 0.1,
intelligence: 0.05,
},
decays: {
hacking: 0,
strength: 0.91,
defense: 0.91,
dexterity: 0.91,
agility: 0.91,
charisma: 0.9,
intelligence: 1,
},
isStealth: true,
growthFunction: () => getRandomIntInclusive(5, 75) / 10,
minCount: 25,
}),
[BladeContractName.bountyHunter]: new Contract({
name: BladeContractName.bountyHunter,
desc:
"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.\n\n" +
"Successfully completing a Bounty Hunter contract will lower the population in your current city, and will also " +
"increase its chaos level.",
baseDifficulty: 250,
difficultyFac: 1.04,
rewardFac: 1.085,
rankGain: 0.9,
hpLoss: 1,
weights: {
hacking: 0,
strength: 0.15,
defense: 0.15,
dexterity: 0.25,
agility: 0.25,
charisma: 0.1,
intelligence: 0.1,
},
decays: {
hacking: 0,
strength: 0.91,
defense: 0.91,
dexterity: 0.91,
agility: 0.91,
charisma: 0.8,
intelligence: 0.9,
},
isKill: true,
growthFunction: () => getRandomIntInclusive(5, 75) / 10,
minCount: 5,
}),
[BladeContractName.retirement]: new Contract({
name: BladeContractName.retirement,
desc:
"Hunt down and retire (kill) rogue Synthoids.\n\n" +
"Successfully completing a Retirement contract will lower the population in your current city, and will also " +
"increase its chaos level.",
baseDifficulty: 200,
difficultyFac: 1.03,
rewardFac: 1.065,
rankGain: 0.6,
hpLoss: 1,
weights: {
hacking: 0,
strength: 0.2,
defense: 0.2,
dexterity: 0.2,
agility: 0.2,
charisma: 0.1,
intelligence: 0.1,
},
decays: {
hacking: 0,
strength: 0.91,
defense: 0.91,
dexterity: 0.91,
agility: 0.91,
charisma: 0.8,
intelligence: 0.9,
},
isKill: true,
growthFunction: () => getRandomIntInclusive(5, 75) / 10,
minCount: 5,
}),
};
}
export function loadContractsData(data: unknown, contracts: Record<BladeContractName, Contract>) {
// loading data as "unknown" and typechecking it down is probably not necessary
// but this will prevent crashes even with malformed savedata
if (!data || typeof data !== "object") return;
assertLoadingType<Record<BladeContractName, unknown>>(data);
for (const contractName of Object.values(BladeContractName)) {
const loadedContract = data[contractName];
if (!(loadedContract instanceof Contract)) continue;
contracts[contractName].loadData(loadedContract);
}
}

View File

@@ -1,42 +0,0 @@
import React from "react";
interface IContract {
desc: JSX.Element;
}
export const Contracts: Record<string, IContract | undefined> = {
Tracking: {
desc: (
<>
Identify and locate Synthoids. This contract involves reconnaissance and information-gathering ONLY. Do NOT
engage. Stealth is of the utmost importance.
<br />
<br />
Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for whatever
city you are currently in.
</>
),
},
"Bounty Hunter": {
desc: (
<>
Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.
<br />
<br />
Successfully completing a Bounty Hunter contract will lower the population in your current city, and will also
increase its chaos level.
</>
),
},
Retirement: {
desc: (
<>
Hunt down and retire (kill) rogue Synthoids.
<br />
<br />
Successfully completing a Retirement contract will lower the population in your current city, and will also
increase its chaos level.
</>
),
},
};

View File

@@ -0,0 +1,58 @@
import { BladeGeneralActionName } from "@enums";
import { GeneralAction } from "../Actions/GeneralAction";
import { BladeburnerConstants } from "./Constants";
export const GeneralActions: Record<BladeGeneralActionName, GeneralAction> = {
[BladeGeneralActionName.training]: new GeneralAction({
name: BladeGeneralActionName.training,
getActionTime: () => 30,
desc:
"Improve your abilities at the Bladeburner unit's specialized training center. Doing this gives experience for " +
"all combat stats and also increases your max stamina.",
}),
[BladeGeneralActionName.fieldAnalysis]: new GeneralAction({
name: BladeGeneralActionName.fieldAnalysis,
getActionTime: () => 30,
desc:
"Mine and analyze Synthoid-related data. This improves the Bladeburner unit's intelligence on Synthoid locations " +
"and activities. Completing this action will improve the accuracy of your Synthoid population estimated in the " +
"current city.\n\n" +
"Does NOT require stamina.",
}),
[BladeGeneralActionName.recruitment]: new GeneralAction({
name: BladeGeneralActionName.recruitment,
getActionTime: function (bladeburner, person) {
const effCharisma = bladeburner.getEffectiveSkillLevel(person, "charisma");
const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90;
return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor));
},
getSuccessChance: function (bladeburner, person) {
return Math.pow(person.skills.charisma, 0.45) / (bladeburner.teamSize - bladeburner.sleeveSize + 1);
},
desc:
"Attempt to recruit members for your Bladeburner team. These members can help you conduct operations.\n\n" +
"Does NOT require stamina.",
}),
[BladeGeneralActionName.diplomacy]: new GeneralAction({
name: BladeGeneralActionName.diplomacy,
getActionTime: () => 60,
desc:
"Improve diplomatic relations with the Synthoid population. Completing this action will reduce the Chaos level in " +
"your current city.\n\n" +
"Does NOT require stamina.",
}),
[BladeGeneralActionName.hyperbolicRegen]: new GeneralAction({
name: BladeGeneralActionName.hyperbolicRegen,
getActionTime: () => 60,
desc:
"Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. This will slowly heal your " +
"wounds and slightly increase your stamina.",
}),
[BladeGeneralActionName.inciteViolence]: new GeneralAction({
name: BladeGeneralActionName.inciteViolence,
getActionTime: () => 60,
desc:
"Purposefully stir trouble in the synthoid community in order to gain a political edge. This will generate " +
"additional contracts and operations, at the cost of increased Chaos.",
}),
};

View File

@@ -1,97 +0,0 @@
import React from "react";
import { newWorkStats, WorkStats } from "../../Work/WorkStats";
interface IGeneral {
desc: JSX.Element;
exp: WorkStats;
}
export const GeneralActions: Record<string, IGeneral | undefined> = {
Training: {
desc: (
<>
Improve your abilities at the Bladeburner unit's specialized training center. Doing this gives experience for
all combat stats and also increases your max stamina.
</>
),
exp: newWorkStats({
strExp: 30,
defExp: 30,
dexExp: 30,
agiExp: 30,
}),
},
"Field Analysis": {
desc: (
<>
Mine and analyze Synthoid-related data. This improves the Bladeburner unit's intelligence on Synthoid locations
and activities. Completing this action will improve the accuracy of your Synthoid population estimated in the
current city.
<br />
<br />
Does NOT require stamina.
</>
),
exp: newWorkStats({
hackExp: 20,
chaExp: 20,
}),
},
Recruitment: {
desc: (
<>
Attempt to recruit members for your Bladeburner team. These members can help you conduct operations.
<br />
<br />
Does NOT require stamina.
</>
),
exp: newWorkStats({
chaExp: 120,
}),
},
Diplomacy: {
desc: (
<>
Improve diplomatic relations with the Synthoid population. Completing this action will reduce the Chaos level in
your current city.
<br />
<br />
Does NOT require stamina.
</>
),
exp: newWorkStats({
chaExp: 120,
}),
},
"Hyperbolic Regeneration Chamber": {
desc: (
<>
Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. This will slowly heal your
wounds and slightly increase your stamina.
<br />
<br />
</>
),
exp: newWorkStats(),
},
"Incite Violence": {
desc: (
<>
Purposefully stir trouble in the synthoid community in order to gain a political edge. This will generate
additional contracts and operations, at the cost of increased Chaos.
</>
),
exp: newWorkStats({
strExp: 10,
defExp: 10,
dexExp: 10,
agiExp: 10,
chaExp: 10,
}),
},
};

View File

@@ -1,24 +0,0 @@
import { getRandomInt } from "../../utils/helpers/getRandomInt";
export const Growths: {
[key: string]: (() => number) | undefined;
["Tracking"]: () => number;
["Bounty Hunter"]: () => number;
["Retirement"]: () => number;
["Investigation"]: () => number;
["Undercover Operation"]: () => number;
["Sting Operation"]: () => number;
["Raid"]: () => number;
["Stealth Retirement Operation"]: () => number;
["Assassination"]: () => number;
} = {
Tracking: () => getRandomInt(5, 75) / 10,
"Bounty Hunter": () => getRandomInt(5, 75) / 10,
Retirement: () => getRandomInt(5, 75) / 10,
Investigation: () => getRandomInt(10, 40) / 10,
"Undercover Operation": () => getRandomInt(10, 40) / 10,
"Sting Operation": () => getRandomInt(3, 40) / 10,
Raid: () => getRandomInt(2, 40) / 10,
"Stealth Retirement Operation": () => getRandomInt(1, 20) / 10,
Assassination: () => getRandomInt(1, 20) / 10,
};

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