Compare commits

..

13 Commits

Author SHA1 Message Date
catloversg da4c7a01dd API: Make ns.spawn return never instead of void (#2798) 2026-05-21 17:59:30 -07:00
catloversg 0e39a5f603 MISC: Allow killing scripts during tutorial (#2797) 2026-05-21 17:57:06 -07:00
Berdes b8dc41faca MISC: Remove extra semicolon in Job page (#2796)
The semi-colon was visible when selecting text that includes it,
rendering the game LiterallyUnplayble™
2026-05-21 17:43:53 -07:00
catloversg cd94288da0 BUGFIX: nano and vim open and save files on wrong servers in edge cases (#2795) 2026-05-21 17:42:52 -07:00
catloversg aaf249df11 DEPS: Update swc (#2794) 2026-05-20 15:05:02 -07:00
TrixTrl 827f881be4 DOC: Fixed trailing } in ns.formulas.weakenEffect (#2793)
Co-authored-by: Trix <Trix@DESKTOP-QAEJLQP>
2026-05-20 15:03:02 -07:00
catloversg 6095f2e1ac MISC: Follow up to #2784 (#2788) 2026-05-19 14:44:24 -07:00
catloversg e0d8a3e201 DEPS: Update dependencies (#2785) 2026-05-18 15:21:09 -07:00
catloversg 016ffbf8c7 MISC: Allow exporting save data even if game fails to load (#2784) 2026-05-18 15:12:04 -07:00
catloversg 3ebe82c60a UI: Show alert if browser is too outdated (#2782) 2026-05-18 15:07:30 -07:00
catloversg 64b25469ea UI: Improve visual effect of "SoA - Flood of Poseidon" augmentation (#2781) 2026-05-18 15:03:59 -07:00
catloversg d060b6b1f0 UI: Show data file names when opening darknet cache files (#2766)
* UI: Show data file names when opening darknet cache files

* Feedback
2026-05-17 11:11:40 -07:00
Snarling f34116c2b1 VERSION: 3.0.2 Initial changes (#2779) 2026-05-17 02:46:01 -04:00
26 changed files with 879 additions and 527 deletions
+30 -28
View File
@@ -1,18 +1,18 @@
{
"name": "bitburner",
"version": "3.0.1",
"version": "3.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "3.0.1",
"version": "3.0.2",
"dependencies": {
"@catloversg/steamworks.js": "0.0.3",
"arg": "^5.0.2",
"electron-log": "^4.4.8",
"electron-store": "^8.1.0",
"lodash": "^4.17.21"
"lodash": "^4.18.1"
}
},
"node_modules/@catloversg/steamworks.js": {
@@ -37,14 +37,15 @@
}
},
"node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
@@ -162,6 +163,22 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
},
"node_modules/find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
@@ -204,9 +221,10 @@
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "6.0.0",
@@ -301,14 +319,6 @@
"node": ">=8"
}
},
"node_modules/punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"engines": {
"node": ">=6"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -348,14 +358,6 @@
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "bitburner",
"version": "3.0.1",
"version": "3.0.2",
"description": "A cyberpunk-themed programming incremental game",
"main": "main.js",
"author": "Daniel Xie, hydroflame, et al.",
@@ -28,6 +28,6 @@
"arg": "^5.0.2",
"electron-log": "^4.4.8",
"electron-store": "^8.1.0",
"lodash": "^4.17.21"
"lodash": "^4.18.1"
}
}
+6 -1
View File
@@ -27,6 +27,12 @@
</div>
<!-- Use esm for top-level await -->
<script type="module">
if (!window.indexedDB || typeof window.indexedDB.databases !== "function") {
const errorMessage = `Your browser is too outdated. Please update your browser.\n\nUserAgent: ${navigator.userAgent}`;
alert(errorMessage);
// This is the simplest way to stop execution in top-level code without using a labeled block or IIFE.
throw new Error(errorMessage);
}
const databaseName = "bitburnerSave";
// Check src/db.ts to see why the current max version is 2. If the database version is greater than this value, it
// means that the code in this file is outdated.
@@ -35,7 +41,6 @@
const database = databases.find((info) => info.name === databaseName);
if (!database) {
alert("There is no save data");
// This is the simplest way to stop execution in top-level code without using a labeled block or IIFE.
throw new Error("There is no save data");
}
if (database.version === undefined || database.version > maxDatabaseVersion) {
+1 -1
View File
@@ -134,7 +134,7 @@ Calculate hack time.
</td><td>
Calculate the security decrease from a weaken operation. Unlike other hacking formulas, weaken effect depends only on thread count and core count, not on server or player properties. The core bonus formula is `1 + (cores - 1) / 16}`<!-- -->.
Calculate the security decrease from a weaken operation. Unlike other hacking formulas, weaken effect depends only on thread count and core count, not on server or player properties. The core bonus formula is `1 + (cores - 1) / 16`<!-- -->.
</td></tr>
@@ -4,7 +4,7 @@
## HackingFormulas.weakenEffect() method
Calculate the security decrease from a weaken operation. Unlike other hacking formulas, weaken effect depends only on thread count and core count, not on server or player properties. The core bonus formula is `1 + (cores - 1) / 16}`<!-- -->.
Calculate the security decrease from a weaken operation. Unlike other hacking formulas, weaken effect depends only on thread count and core count, not on server or player properties. The core bonus formula is `1 + (cores - 1) / 16`<!-- -->.
**Signature:**
+2 -2
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: ScriptArg[]): void;
spawn(script: string, threadOrOptions?: number | SpawnOptions, ...args: ScriptArg[]): never;
```
## Parameters
@@ -82,7 +82,7 @@ Additional arguments to pass into the new script that is being run.
**Returns:**
void
never
## Remarks
+724 -362
View File
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -1,7 +1,7 @@
{
"name": "bitburner",
"license": "SEE LICENSE IN license.txt",
"version": "3.0.1",
"version": "3.0.2",
"main": "electron-main.js",
"author": {
"name": "Daniel Xie, hydroflame, et al."
@@ -18,7 +18,7 @@
"@mui/material": "^5.18.0",
"@mui/styles": "^5.18.0",
"@mui/system": "^5.18.0",
"@swc/wasm-web": "^1.9.3",
"@swc/wasm-web": "^1.15.33",
"@types/estree": "^1.0.2",
"@types/react-syntax-highlighter": "^15.5.13",
"acorn": "^8.11.3",
@@ -56,20 +56,20 @@
"description": "A cyberpunk-themed incremental game",
"devDependencies": {
"@babel/core": "^7.29.0",
"@babel/preset-env": "^7.29.2",
"@babel/preset-env": "^7.29.5",
"@babel/preset-react": "^7.28.5",
"@babel/preset-typescript": "^7.28.5",
"@electron/packager": "^18.4.4",
"@mathjax/src": "^4.1.0",
"@mathjax/src": "^4.1.2",
"@microsoft/api-documenter": "^7.26.34",
"@microsoft/api-extractor": "^7.52.13",
"@microsoft/api-extractor": "^7.58.7",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.1",
"@swc/core": "^1.12.14",
"@swc/core": "^1.15.33",
"@types/babel__standalone": "^7.1.9",
"@types/bcryptjs": "^2.4.4",
"@types/convert-source-map": "^2.0.3",
"@types/jest": "^30.0.0",
"@types/lodash": "^4.17.20",
"@types/lodash": "^4.17.24",
"@types/react": "^17.0.89",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^17.0.26",
@@ -91,7 +91,7 @@
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jsdom": "^26.1.0",
"lodash": "^4.17.23",
"lodash": "^4.18.1",
"monaco-editor": "^0.55.1",
"monaco-editor-webpack-plugin": "^7.1.1",
"prettier": "^2.8.8",
@@ -99,10 +99,10 @@
"style-loader": "^4.0.0",
"tinybench": "^6.0.0",
"typescript": "^5.9.3",
"webpack": "^5.105.4",
"webpack-bundle-analyzer": "^5.2.0",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2",
"webpack": "^5.106.2",
"webpack-bundle-analyzer": "^5.3.0",
"webpack-cli": "^7.0.2",
"webpack-dev-server": "^5.2.4",
"xml-formatter": "^3.6.7"
},
"overrides": {
+4 -51
View File
@@ -4,8 +4,8 @@
* Constants for specific mechanics or features will NOT be here.
*/
export const CONSTANTS = {
VersionString: "3.0.1",
isDevBranch: false,
VersionString: "3.0.2",
isDevBranch: true,
isInTestEnvironment: globalThis.process?.env?.JEST_WORKER_ID !== undefined,
VersionNumber: 51,
@@ -111,57 +111,10 @@ export const CONSTANTS = {
// Also update Documentation/doc/en/changelog.md when appropriate (when doing a release)
LatestUpdate: `
## v3.0.1: 17 May 2026
### BREAKING CHANGES
- Change getServer return type; rename getServerAuthDetails and add missing dnet properties (#2746) (@ficocelliguy)
## v3.0.2: Dev version last updated 17 May 2026
### MISC
- Cache reward fixes (#2731) (@ficocelliguy)
- Fix typo in darknet authentication response message (#2734) (@catloversg)
- Fix: Tutorial links to outdated faq url (#2733) (@catloversg)
- Fix: Player can switch tabs without losing focus on current work (#2724) (@catloversg)
- Add new command to upload a directory (#2659) (@hexagonrecursion)
- Add HJKL key mappings for infiltration arrows (#2742) (@mahlquistj)
- Prevent generating malformed darknet server hostname (#2744) (@catloversg)
- Support angle bracket type assertions in RAM calculation (#2751) (@catloversg)
- Fix typo in augmentation description (#2760) (@gmcew)
- Reduce the RAM cost of ns.rm() to match ns.scp() (#2761) (@NagaOuroboros)
- Temporarily remove darknet servers with unusual hostnames (#2757) (@catloversg)
- Fix: Duplicate .lit and .cache files can be generated in dnet (#2763) (@catloversg)
- Allow getFunctionRamCost to get base RAM cost for scripts (#2771) (@Mathekatze)
### DOCUMENTATION
- Remove TS type annotation from doc example script (#2721) (@ficocelliguy)
- Update list of RFA community tools (#2722) (@CTNOriginals)
- Fix incorrect cloud API example (#2738) (@catloversg)
- Remove non-existent influence namespace in Darknet documentation (#2748) (@Berdes)
- Remove spoiler for Offline scripts and bonus time page and make it accessible early-game (#2749) (@Berdes)
- Clarify ns.scp and ns.isRunning (#2769) (@catloversg)
### SPOILER CHANGES - UI
- Show hints of Gang mechanic in pre-endgame (#2723) (@catloversg)
- Break out Darknet BN and player mults as separate entries (#2745) (@gmcew)
### SPOILER CHANGES - MISC
- Cancel faction work instead of finishing it when creating gang (#2726) (@catloversg)
- Lower darknet BN money mults on hard nodes (#2743) (@ficocelliguy)
- Change getDarkwebPrograms return type to ProgramName[] (#2754) (@catloversg)
- Fix: getAugmentationBasePrice ignores bitnode mult for SoA augs (#2756) (@SAY-5)
- Rebalance hash base cost of generating coding contract (#2759) (@gmcew)
- Ensure induceServerMigration moves servers randomly (#2767) (@catloversg)
- Do not decrease player reputation when failing bladeburner actions (#2768) (@catloversg)
### CODEBASE/REFACTOR/WORKFLOW/JEST/TOOL/DEPS
- Update action versions (#2718) (@Snarling)
- Remove duplicate getStockFromSymbol function (#2725) (@catloversg)
- Update game version (#2732) (@catloversg)
- Harden saving to avoid save data corruption (#2755) (@catloversg)
- No changes yet
`,
} as const;
+4 -6
View File
@@ -13,6 +13,7 @@ import { resolveCacheFilePath } from "../../Paths/CacheFilePath";
import type { CacheResult } from "@nsdefs";
import { addClue, cctCooldownReached } from "./effects";
import { getBitNodeMultipliers } from "../../BitNode/BitNode";
import { pluralize } from "../../utils/I18nUtils";
export const generateCacheFilename = (isPhishingCache: boolean, prefix?: string) => {
const filenamePrefix = prefix ?? cachePrefixes[Math.floor(Math.random() * cachePrefixes.length)];
@@ -117,14 +118,11 @@ export const getStockReward = (difficulty: number): string => {
};
export const getDataFileReward = (difficulty: number, server: DarknetServer): string => {
const currentDataFiles = server.textFiles.size;
addClue(server);
addClue(server);
const dataFilesGained = server.textFiles.size - currentDataFiles;
if (dataFilesGained === 0) {
const dataFiles = [...addClue(server), ...addClue(server)];
if (dataFiles.length === 0) {
return getMoneyReward(difficulty);
}
return `You have discovered a data file cache!`;
return `You have discovered ${pluralize(dataFiles.length, "data file cache")}: ${dataFiles.join(", ")}.`;
};
export const getProgramAndStockMarketRelatedRewards = (difficulty: number): string => {
+14 -6
View File
@@ -121,12 +121,14 @@ export const calculatePasswordAttemptChaGain = (server: DarknetServerData, threa
};
// TODO: balance password clue spawn rate
export const addClue = (server: DarknetServer) => {
export const addClue = (server: DarknetServer): string[] => {
const files = [];
// Basic mechanics hints
if ((Math.random() < 0.7 && server.difficulty <= 3) || Math.random() < 0.1) {
const hint: LiteratureName = hintLiterature[Math.floor(Math.random() * hintLiterature.length)];
if (hint && !server.messages.includes(hint)) {
server.messages.push(hint);
files.push(hint);
}
}
@@ -137,7 +139,8 @@ export const addClue = (server: DarknetServer) => {
const start = Math.floor(Math.random() * (commonPasswordDictionary.length - length));
const commonPasswords = commonPasswordDictionary.slice(start, start + length).join(", ");
server.writeToTextFile(hintFileName, `Some common passwords include ${commonPasswords}`);
return;
files.push(hintFileName);
return files;
}
// connected neighboring server's password (does not include server name)
@@ -150,7 +153,8 @@ export const addClue = (server: DarknetServer) => {
const neighboringServer = neighboringServerName ? getDarknetServer(neighboringServerName) : null;
if (neighboringServer) {
server.writeToTextFile(passwordHintName, `Remember this password: ${neighboringServer.password}`);
return;
files.push(passwordHintName);
return files;
}
}
@@ -161,7 +165,8 @@ export const addClue = (server: DarknetServer) => {
if (targetServer) {
const contents = `Server: ${targetServer.hostname} Password: "${targetServer.password}"`;
server.writeToTextFile(hintFileName, contents);
return;
files.push(hintFileName);
return files;
}
}
@@ -169,7 +174,8 @@ export const addClue = (server: DarknetServer) => {
const hintFileName = getClueFileName(notebookFileNames);
const loreNote = packetSniffPhrases[Math.floor(Math.random() * packetSniffPhrases.length)];
server.writeToTextFile(hintFileName, loreNote);
return;
files.push(hintFileName);
return files;
}
if (Math.random() < 0.7) {
@@ -179,9 +185,11 @@ export const addClue = (server: DarknetServer) => {
const [containedChar1, containedChar2] = getTwoCharsInPassword(targetServer.password);
const hint = `The password for ${targetServer.hostname} contains ${containedChar1} and ${containedChar2}`;
server.writeToTextFile(hintFileName, hint);
return;
files.push(hintFileName);
return files;
}
}
return files;
};
export const getClueFileName = (fileNameList: readonly string[]): TextFilePath => {
File diff suppressed because one or more lines are too long
+16 -4
View File
@@ -43,7 +43,7 @@ export function Cyberpunk2077Game({ stage }: IProps): React.ReactElement {
<Typography variant="h5" color={Settings.theme.primary}>
Targets:{" "}
{stage.answers.map((a, i) => {
if (i == stage.currentAnswerIndex)
if (i === stage.currentAnswerIndex)
return (
<span key={`${i}`} style={{ fontSize: "1em", color: Settings.theme.infolight }}>
{a}&nbsp;
@@ -64,20 +64,32 @@ export function Cyberpunk2077Game({ stage }: IProps): React.ReactElement {
gap: 1,
}}
>
{flatGrid.map((item, idx) => (
{flatGrid.map((item, idx) => {
let borderStyle = "solid";
let borderColor = "transparent";
if (item.selected) {
borderColor = Settings.theme.infolight;
} else if (hasAugment && item.content === stage.answers[stage.currentAnswerIndex]) {
borderStyle = "dashed";
borderColor = Settings.theme.warning;
}
return (
<Typography
key={idx}
sx={{
fontSize: fontSize,
color: item.color,
border: item.selected ? `2px solid ${Settings.theme.infolight}` : "unset",
// Always use a 2px border to avoid small symbol position shifts caused by size changes (element vs
// element + 2px border).
border: `2px ${borderStyle} ${borderColor}`,
lineHeight: "unset",
p: item.selected ? "2px" : "4px",
}}
>
{item.content}
</Typography>
))}
);
})}
</Box>
</Paper>
</>
+1 -1
View File
@@ -19,7 +19,7 @@ export function JobRoot(): React.ReactElement {
}
return (
<Box key={companyName} sx={{ marginBottom: "20px" }}>
<GenericLocation location={location} showBackButton={false} />;
<GenericLocation location={location} showBackButton={false} />
</Box>
);
});
+1 -9
View File
@@ -8,20 +8,12 @@ import { workerScripts } from "./WorkerScripts";
import { GetAllServers, GetServer } from "../Server/AllServers";
import { AddRecentScript } from "./RecentScripts";
import { ITutorial } from "../InteractiveTutorial";
import { AlertEvents } from "../ui/React/AlertManager";
import { handleUnknownError } from "../utils/ErrorHandler";
import { roundToTwo } from "../utils/helpers/roundToTwo";
import { BaseServer } from "../Server/BaseServer";
export function killWorkerScript(ws: WorkerScript): boolean {
if (ITutorial.isRunning) {
AlertEvents.emit("Processes cannot be killed during the tutorial.");
return false;
}
export function killWorkerScript(ws: WorkerScript): void {
stopAndCleanUpWorkerScript(ws);
return true;
}
export function killWorkerScriptByPid(pid: number, killer?: WorkerScript): boolean {
+1 -4
View File
@@ -675,18 +675,15 @@ export const ns: InternalAPI<NSFull> = {
}
helpers.log(ctx, () => "About to exit...");
const killed = killWorkerScript(ctx.workerScript);
killWorkerScript(ctx.workerScript);
if (runOpts.spawnDelay === 0) {
helpers.log(ctx, () => `Executing '${path}' immediately`);
spawnCb();
}
if (killed) {
// This prevents error messages about statements after the spawn()
// trying to be executed when the script is dead.
throw new ScriptDeath(ctx.workerScript);
}
},
self: (ctx) => () => {
const runningScript = helpers.getRunningScript(ctx, ctx.workerScript.pid);
+2 -2
View File
@@ -6296,7 +6296,7 @@ interface HackingFormulas {
* Calculate the security decrease from a weaken operation.
* Unlike other hacking formulas, weaken effect depends only on thread count and
* core count, not on server or player properties. The core bonus formula is
* `1 + (cores - 1) / 16}`.
* `1 + (cores - 1) / 16`.
* @param threads - Number of threads running weaken.
* @param cores - Number of cores on the host server. Default 1.
* @returns The security decrease amount.
@@ -7950,7 +7950,7 @@ export interface NS {
* @param threadOrOptions - Either an integer number of threads for new script, or a {@link SpawnOptions} object. Threads defaults to 1 and spawnDelay defaults to 10,000 ms.
* @param args - Additional arguments to pass into the new script that is being run.
*/
spawn(script: string, threadOrOptions?: number | SpawnOptions, ...args: ScriptArg[]): void;
spawn(script: string, threadOrOptions?: number | SpawnOptions, ...args: ScriptArg[]): never;
/**
* Returns the currently running script.
+6 -1
View File
@@ -182,7 +182,12 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
const clickPage = useCallback(
(page: Page) => {
if (page == Page.ScriptEditor || page == Page.Documentation || page == Page.Options) {
if (page == Page.ScriptEditor) {
Router.toPage(page, {
files: new Map(),
options: { vim: Settings.MonacoDefaultToVim, hostname: Player.currentServer },
});
} else if (page == Page.Documentation || page == Page.Options) {
Router.toPage(page, {});
} else if (isSimplePage(page)) {
Router.toPage(page);
+6 -9
View File
@@ -1,9 +1,9 @@
import { Terminal } from "../../../Terminal";
import { ScriptEditorRouteOptions, Page } from "../../../ui/Router";
import { Page } from "../../../ui/Router";
import { Router } from "../../../ui/GameRoot";
import { BaseServer } from "../../../Server/BaseServer";
import type { BaseServer } from "../../../Server/BaseServer";
import { type ScriptFilePath, hasScriptExtension, isLegacyScript } from "../../../Paths/ScriptFilePath";
import { TextFilePath, hasTextExtension } from "../../../Paths/TextFilePath";
import { type TextFilePath, hasTextExtension } from "../../../Paths/TextFilePath";
import { getGlobbedFileMap } from "../../../Paths/GlobbedFiles";
import { sendDeprecationNotice } from "./deprecation";
import { getFileType, getFileTypeFeature } from "../../../utils/ScriptTransformer";
@@ -14,6 +14,7 @@ import { hasCacheExtension } from "../../../Paths/CacheFilePath";
interface EditorParameters {
args: (string | number | boolean)[];
server: BaseServer;
vim: boolean;
}
function getScriptTemplate(path: string): string {
@@ -33,11 +34,7 @@ export async function main(ns) {
}
}
export function commonEditor(
command: string,
{ args, server }: EditorParameters,
options?: ScriptEditorRouteOptions,
): void {
export function commonEditor(command: string, { args, server, vim }: EditorParameters): void {
if (args.length < 1) return Terminal.error(`Incorrect usage of ${command} command. Usage: ${command} [scriptname]`);
const files = new Map<ScriptFilePath | TextFilePath, string>();
let hasLegacyScript = false;
@@ -76,5 +73,5 @@ export function commonEditor(
if (hasLegacyScript) {
sendDeprecationNotice();
}
Router.toPage(Page.ScriptEditor, { files, options });
Router.toPage(Page.ScriptEditor, { files, options: { vim, hostname: server.hostname } });
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { BaseServer } from "../../Server/BaseServer";
import type { BaseServer } from "../../Server/BaseServer";
import { commonEditor } from "./common/editor";
export function nano(args: (string | number | boolean)[], server: BaseServer): void {
return commonEditor("nano", { args, server }, { vim: false });
return commonEditor("nano", { args, server, vim: false });
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { BaseServer } from "../../Server/BaseServer";
import type { BaseServer } from "../../Server/BaseServer";
import { commonEditor } from "./common/editor";
export function vim(args: (string | number | boolean)[], server: BaseServer): void {
return commonEditor("vim", { args, server }, { vim: true });
return commonEditor("vim", { args, server, vim: true });
}
+3 -4
View File
@@ -72,7 +72,6 @@ import { V2Modal } from "../utils/V2Modal";
import { useRerender } from "./React/hooks";
import { HistoryProvider } from "./React/Documentation";
import { GoRoot } from "../Go/ui/GoRoot";
import { Settings } from "../Settings/Settings";
import { isBitNodeFinished } from "../BitNode/BitNodeUtils";
import { UIEventEmitter, UIEventType } from "./UIEventEmitter";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
@@ -351,9 +350,9 @@ export function GameRoot(): React.ReactElement {
case Page.ScriptEditor: {
mainPage = (
<ScriptEditorRoot
files={pageWithContext.files ?? new Map()}
hostname={pageWithContext.options?.hostname ?? Player.getCurrentServer().hostname}
vim={pageWithContext.options === undefined ? Settings.MonacoDefaultToVim : pageWithContext.options.vim}
files={pageWithContext.files}
hostname={pageWithContext.options.hostname}
vim={pageWithContext.options.vim}
/>
);
break;
+2
View File
@@ -61,6 +61,8 @@ export function LoadingScreen(): React.ReactElement {
<Grid item>
<Typography>
If the game fails to load, consider <a href="?noScripts">killing all scripts</a>
<br />
You can export your save data at <a href="./export.html">export.html</a>
</Typography>
</Grid>
)}
+2 -2
View File
@@ -18,7 +18,7 @@ export type PageContext<T extends Page> = T extends ComplexPage.BitVerse
: T extends ComplexPage.FactionAugmentations
? { faction: Faction }
: T extends ComplexPage.ScriptEditor
? { files?: Map<ScriptFilePath | TextFilePath, string>; options?: ScriptEditorRouteOptions }
? { files: Map<ScriptFilePath | TextFilePath, string>; options: ScriptEditorRouteOptions }
: T extends ComplexPage.Location
? { location: Location }
: T extends ComplexPage.ImportSave
@@ -43,7 +43,7 @@ export type PageWithContext =
export interface ScriptEditorRouteOptions {
vim: boolean;
hostname?: string;
hostname: string;
}
/** The router keeps track of player navigation/routing within the game. */
+19
View File
@@ -22,3 +22,22 @@ if (window.indexedDB) {
writable: false,
});
}
// Some players use really old browser versions on unsupported OSes such as Windows 7. Intl.Segmenter and other APIs are
// not supported in these browsers, so they will only see a black screen when loading the game. We should show an alert
// to notify them that they should update their browser, if possible.
if (typeof Intl.Segmenter !== "function" || typeof String.prototype.toWellFormed !== "function") {
const errorMessage = `Your browser is too outdated. Please update your browser.\n\nUserAgent: ${navigator.userAgent}`;
alert(errorMessage);
const rootElement = document.getElementById("root");
if (rootElement) {
rootElement.innerText = errorMessage;
rootElement.style.color = "red";
rootElement.style.fontSize = "20px";
rootElement.style.justifyContent = "center";
}
// If the browser does not support Intl.Segmenter, initialization of graphemeSegmenter in StringHelperFunctions.ts
// will fail, so this throw is technically unnecessary. However, if toWellFormed is not supported, the game can still
// load normally while darknet initialization fails, potentially causing UI issues such as an empty darknet tab.
throw new Error(errorMessage);
}
+1
View File
@@ -9,5 +9,6 @@ mkdir .app
# Should be all the files needed.
cp index.html .app
cp export.html .app
cp favicon.ico .app
cp -r dist .app