mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-19 20:55:44 +01:00
NETSCRIPT: Add .script deprecation notice & migration guide (#1007)
This commit is contained in:
parent
9827fda4a4
commit
a433c8284c
@ -51,5 +51,6 @@
|
||||
|
||||
## Migration
|
||||
|
||||
- [v1.0.0 script migration guide](migrations/v1.md)
|
||||
- [v2.0.0 script migration guide](migrations/v2.md)
|
||||
- [Bitburner v1.0.0 script migration guide](migrations/v1.md)
|
||||
- [Bitburner v2.0.0 script migration guide](migrations/v2.md)
|
||||
- [Netscript 2 migration guide (.script to .js)](migrations/ns2.md)
|
||||
|
78
src/Documentation/doc/migrations/ns2.md
Normal file
78
src/Documentation/doc/migrations/ns2.md
Normal file
@ -0,0 +1,78 @@
|
||||
# "Netscript 2" Migration Guide
|
||||
|
||||
The game allows two script formats:
|
||||
|
||||
- `.script` (also sometimes called "Netscript 1" or "NS1") files are ran through an interpreter that is based on a version of Javascript from 2009 (ES5). These files are no longer actively supported and should be converted to the newer `.js` format.
|
||||
|
||||
- `.js` (also sometimes called "Netscript 2" or "NS2") files are native javascript that is ran directly by the web browser. Modern features of javascript that are supported by your web browser are supported in `.js` files, because they are run as native js.
|
||||
|
||||
Support for `.script` files will be completely removed in future version 3.0. Some basic autoconversion will be attempted at that time, but it is highly recommended to convert your remaining `.script` files to the `.js` format before that, to ensure correctness and, if needed, to familiarize yourself with the `.js` format requirements.
|
||||
|
||||
## Why do I have to change anything?
|
||||
|
||||
Since all ES5 code is still valid Javascript, you may be wondering why the old code doesn't just work when renamed to `.js`. In this section, some key differences are explained.
|
||||
|
||||
- **API access method**: In `.script` files, the game API is available at top-level scope within the file, as if they were at global scope. In `.js` files, the game API is passed as an argument into a script's `main` function, and is not available at top-level scope.
|
||||
- **Execution differences**: Running a `.script` file begins code execution at the top of the file. Running a `.js` file launches the `main` function (passing the game API in as the first argument, typically named `ns`).
|
||||
- **Timing differences**: In `.script` files, code execution contains automatic delays between every statement. In `.js` files, the code is being run natively so there are no builtin delays, so any needed delays must be added manually.
|
||||
|
||||
## Basic steps for script migration
|
||||
|
||||
1. Wrap the entire script inside of an exported main function, like so:
|
||||
|
||||
```js
|
||||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
// your code here
|
||||
}
|
||||
```
|
||||
|
||||
2. Add `ns.` as a prefix to all game functions or objects (such as `ns.hack()` or `ns.args`).
|
||||
3. If a function returns a `Promise`, you need to put the `await` keyword before it (With the JSDoc comment you can hover over the function to see the return type). Note that only functions declared as `async` are allowed to `await` anything, and typically you should also await the calls to your own `async` functions.
|
||||
4. Because there are no forced delays, an infinite loop will cause the game to hang if a delay is not manually added. Such loops should `await` a function (usually `ns.sleep()`) at least once to avoid this.
|
||||
5. The `var` keyword is rarely used in modern js for declaring variables. `let` or `const` (if the variable is never reassigned) keywords are preferred. This change is not required.
|
||||
|
||||
## Example migration
|
||||
|
||||
Original (`early-hacking-template.script`):
|
||||
|
||||
```js
|
||||
var target = "n00dles";
|
||||
var moneyThresh = getServerMaxMoney(target) * 0.9;
|
||||
var securityThresh = getServerMinSecurityLevel(target) + 5;
|
||||
|
||||
while (true) {
|
||||
if (getServerSecurityLevel(target) > securityThresh) {
|
||||
weaken(target);
|
||||
} else if (getServerMoneyAvailable(target) < moneyThresh) {
|
||||
grow(target);
|
||||
} else {
|
||||
hack(target);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Migrated (`early-hacking-template.js`):
|
||||
|
||||
```js
|
||||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const target = "n00dles";
|
||||
const moneyThresh = ns.getServerMaxMoney(target) * 0.9;
|
||||
const securityThresh = ns.getServerMinSecurityLevel(target) + 5;
|
||||
|
||||
while(true) {
|
||||
if (ns.getServerSecurityLevel(target) > securityThresh) {
|
||||
await ns.weaken(target);
|
||||
} else if (ns.getServerMoneyAvailable(target) < moneyThresh) {
|
||||
await ns.grow(target):
|
||||
} else {
|
||||
await ns.hack(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Additional problems or edge cases
|
||||
|
||||
To get additional help with the migration join the [official Discord server](https://discord.gg/TFc3hKD).
|
@ -40,6 +40,7 @@ import file37 from "!!raw-loader!./doc/programming/go_algorithms.md";
|
||||
import file38 from "!!raw-loader!./doc/programming/hackingalgorithms.md";
|
||||
import file39 from "!!raw-loader!./doc/programming/learn.md";
|
||||
import file40 from "!!raw-loader!./doc/programming/remote_api.md";
|
||||
import file41 from "!!raw-loader!./doc/migrations/ns2.md";
|
||||
|
||||
interface Document {
|
||||
default: string;
|
||||
@ -86,3 +87,4 @@ AllPages["programming/go_algorithms.md"] = file37;
|
||||
AllPages["programming/hackingalgorithms.md"] = file38;
|
||||
AllPages["programming/learn.md"] = file39;
|
||||
AllPages["programming/remote_api.md"] = file40;
|
||||
AllPages["migrations/ns2.md"] = file41;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import Button from "@mui/material/Button";
|
||||
import { MD } from "../../ui/MD/MD";
|
||||
@ -6,10 +6,15 @@ import { MD } from "../../ui/MD/MD";
|
||||
import { getPage } from "../root";
|
||||
import { Navigator, useHistory } from "../../ui/React/Documentation";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { resolveFilePath } from "../../Paths/FilePath";
|
||||
import { asFilePath, resolveFilePath } from "../../Paths/FilePath";
|
||||
|
||||
export function DocumentationRoot(): React.ReactElement {
|
||||
export function DocumentationRoot({ docPage }: { docPage?: string }): React.ReactElement {
|
||||
const history = useHistory();
|
||||
const [deepLink, setDeepLink] = useState(docPage);
|
||||
if (deepLink !== undefined) {
|
||||
history.push(asFilePath(deepLink));
|
||||
setDeepLink(undefined);
|
||||
}
|
||||
const page = getPage(history.page);
|
||||
const navigator = {
|
||||
navigate(relPath: string, external: boolean) {
|
||||
|
@ -205,7 +205,10 @@ export const GoInstructionsPage = (): React.ReactElement => {
|
||||
<Typography>
|
||||
* You can place routers and look at the board state via the "ns.go" api. For more details, go to the IPvGO
|
||||
page in the{" "}
|
||||
<Link style={{ cursor: "pointer" }} onClick={() => Router.toPage(Page.Documentation)}>
|
||||
<Link
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => Router.toPage(Page.Documentation, { docPage: "programming/go_algorithms.md" })}
|
||||
>
|
||||
Bitburner Documentation
|
||||
</Link>
|
||||
<br />
|
||||
|
@ -166,7 +166,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
|
||||
(page: Page) => {
|
||||
if (page === Page.Job) {
|
||||
Router.toPage(page, { location: Locations[Object.keys(Player.jobs)[0]] });
|
||||
} else if (page == Page.ScriptEditor) {
|
||||
} else if (page == Page.ScriptEditor || page == Page.Documentation) {
|
||||
Router.toPage(page, {});
|
||||
} else if (isSimplePage(page)) {
|
||||
Router.toPage(page);
|
||||
|
22
src/Terminal/commands/common/deprecation.tsx
Normal file
22
src/Terminal/commands/common/deprecation.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
import { Terminal } from "../../../Terminal";
|
||||
import { Settings } from "../../../Settings/Settings";
|
||||
import { Link, Typography } from "@mui/material";
|
||||
import { Router } from "../../../ui/GameRoot";
|
||||
import { Page } from "../../../ui/Router";
|
||||
|
||||
export function sendDeprecationNotice() {
|
||||
return Terminal.printRaw(
|
||||
<Typography sx={{ color: Settings.theme.warning }}>
|
||||
NOTICE: Accessing the Netscript API via .script files is deprecated and will be removed in a future update.{" "}
|
||||
<Link
|
||||
style={{ cursor: "pointer" }}
|
||||
color="inherit"
|
||||
onClick={() => Router.toPage(Page.Documentation, { docPage: "migrations/ns2.md" })}
|
||||
>
|
||||
Here are instructions
|
||||
</Link>{" "}
|
||||
to migrate your scripts to .js files instead.
|
||||
</Typography>,
|
||||
);
|
||||
}
|
@ -6,6 +6,7 @@ import { CursorPositions } from "../../../ScriptEditor/CursorPositions";
|
||||
import { ScriptFilePath, hasScriptExtension } from "../../../Paths/ScriptFilePath";
|
||||
import { TextFilePath, hasTextExtension } from "../../../Paths/TextFilePath";
|
||||
import { getGlobbedFileMap } from "../../../Paths/GlobbedFiles";
|
||||
import { sendDeprecationNotice } from "./deprecation";
|
||||
|
||||
// 2.3: Globbing implementation was removed from this file. Globbing will be reintroduced as broader functionality and integrated here.
|
||||
|
||||
@ -30,12 +31,14 @@ export function commonEditor(
|
||||
): void {
|
||||
if (args.length < 1) return Terminal.error(`Incorrect usage of ${command} command. Usage: ${command} [scriptname]`);
|
||||
const files = new Map<ScriptFilePath | TextFilePath, string>();
|
||||
let hasNs1 = false;
|
||||
for (const arg of args) {
|
||||
const pattern = String(arg);
|
||||
|
||||
// Glob of existing files
|
||||
if (pattern.includes("*") || pattern.includes("?")) {
|
||||
for (const [path, file] of getGlobbedFileMap(pattern, server, Terminal.currDir)) {
|
||||
if (path.endsWith(".script")) hasNs1 = true;
|
||||
files.set(path, file.content);
|
||||
}
|
||||
continue;
|
||||
@ -47,10 +50,14 @@ export function commonEditor(
|
||||
if (!hasScriptExtension(path) && !hasTextExtension(path)) {
|
||||
return Terminal.error(`${command}: Only scripts or text files can be edited. Invalid file type: ${arg}`);
|
||||
}
|
||||
if (path.endsWith(".script")) hasNs1 = true;
|
||||
const file = server.getContentFile(path);
|
||||
const content = file ? file.content : isNs2(path) ? newNs2Template : "";
|
||||
files.set(path, content);
|
||||
if (content === newNs2Template) CursorPositions.saveCursor(path, { row: 3, column: 5 });
|
||||
}
|
||||
if (hasNs1) {
|
||||
sendDeprecationNotice();
|
||||
}
|
||||
Router.toPage(Page.ScriptEditor, { files, options });
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import { formatRam } from "../../ui/formatNumber";
|
||||
import { ScriptArg } from "@nsdefs";
|
||||
import { isPositiveInteger } from "../../types";
|
||||
import { ScriptFilePath } from "../../Paths/ScriptFilePath";
|
||||
import { sendDeprecationNotice } from "./common/deprecation";
|
||||
|
||||
export function runScript(path: ScriptFilePath, commandArgs: (string | number | boolean)[], server: BaseServer): void {
|
||||
// This takes in the absolute filepath, see "run.ts"
|
||||
@ -52,6 +53,9 @@ export function runScript(path: ScriptFilePath, commandArgs: (string | number |
|
||||
const success = startWorkerScript(runningScript, server);
|
||||
if (!success) return Terminal.error(`Failed to start script`);
|
||||
|
||||
if (path.endsWith(".script")) {
|
||||
sendDeprecationNotice();
|
||||
}
|
||||
Terminal.print(
|
||||
`Running script with ${numThreads} thread(s), pid ${runningScript.pid} and args: ${JSON.stringify(args)}.`,
|
||||
);
|
||||
|
@ -280,7 +280,7 @@ export function GameRoot(): React.ReactElement {
|
||||
break;
|
||||
}
|
||||
case Page.Documentation: {
|
||||
mainPage = <DocumentationRoot />;
|
||||
mainPage = <DocumentationRoot docPage={pageWithContext.docPage} />;
|
||||
break;
|
||||
}
|
||||
case Page.DevMenu: {
|
||||
|
@ -29,7 +29,6 @@ export enum SimplePage {
|
||||
StockMarket = "Stock Market",
|
||||
Terminal = "Terminal",
|
||||
Travel = "Travel",
|
||||
Documentation = "Documentation",
|
||||
Work = "Work",
|
||||
BladeburnerCinematic = "Bladeburner Cinematic",
|
||||
Loading = "Loading",
|
||||
@ -48,6 +47,7 @@ export enum ComplexPage {
|
||||
ScriptEditor = "Script Editor",
|
||||
Location = "Location",
|
||||
ImportSave = "Import Save",
|
||||
Documentation = "Documentation",
|
||||
}
|
||||
|
||||
// Using the same name as both type and object to mimic enum-like behavior.
|
||||
@ -71,6 +71,8 @@ export type PageContext<T extends Page> = T extends ComplexPage.BitVerse
|
||||
? { location: Location }
|
||||
: T extends ComplexPage.ImportSave
|
||||
? { base64Save: string; automatic?: boolean }
|
||||
: T extends ComplexPage.Documentation
|
||||
? { docPage?: string }
|
||||
: never;
|
||||
|
||||
export type PageWithContext =
|
||||
@ -82,6 +84,7 @@ export type PageWithContext =
|
||||
| ({ page: ComplexPage.ScriptEditor } & PageContext<ComplexPage.ScriptEditor>)
|
||||
| ({ page: ComplexPage.Location } & PageContext<ComplexPage.Location>)
|
||||
| ({ page: ComplexPage.ImportSave } & PageContext<ComplexPage.ImportSave>)
|
||||
| ({ page: ComplexPage.Documentation } & PageContext<ComplexPage.Documentation>)
|
||||
| { page: SimplePage };
|
||||
|
||||
export interface ScriptEditorRouteOptions {
|
||||
|
Loading…
Reference in New Issue
Block a user