diff --git a/markdown/bitburner.bladeburner.getcurrentaction.md b/markdown/bitburner.bladeburner.getcurrentaction.md index 35b2e1cca..2e2e9b55c 100644 --- a/markdown/bitburner.bladeburner.getcurrentaction.md +++ b/markdown/bitburner.bladeburner.getcurrentaction.md @@ -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 player’s current Bladeburner action. +Object that represents the player’s current Bladeburner action, or null if no action is being performed. ## Remarks RAM cost: 1 GB -Returns an object that represents the player’s current Bladeburner action. If the player is not performing an action, the function will return an object with the ‘type’ property set to “Idle”. - diff --git a/src/NetscriptFunctions/Bladeburner.ts b/src/NetscriptFunctions/Bladeburner.ts index 3cbcd156d..60016fe42 100644 --- a/src/NetscriptFunctions/Bladeburner.ts +++ b/src/NetscriptFunctions/Bladeburner.ts @@ -101,8 +101,7 @@ export function NetscriptBladeburner(): InternalAPI { }, getCurrentAction: (ctx) => () => { const bladeburner = getBladeburner(ctx); - // Temporary bad return type to not be an API break (idle should just return null) - if (!bladeburner.action) return { type: "Idle", name: "Idle" }; + if (!bladeburner.action) return null; return { ...bladeburner.action }; }, getActionTime: (ctx) => (type, name) => { diff --git a/src/SaveObject.ts b/src/SaveObject.ts index 3c77948c9..41257697b 100644 --- a/src/SaveObject.ts +++ b/src/SaveObject.ts @@ -42,6 +42,8 @@ import { SaveData } from "./types"; import { SaveDataError, canUseBinaryFormat, decodeSaveData, encodeJsonSaveString } from "./utils/SaveDataUtils"; import { isBinaryFormat } from "../electron/saveDataBinaryFormat"; import { downloadContentAsFile } from "./utils/FileUtils"; +import { showAPIBreaks } from "./utils/APIBreaks/APIBreak"; +import { breakInfos261 } from "./utils/APIBreaks/2.6.1"; import { handleGetSaveDataError } from "./Netscript/ErrorMessages"; /* SaveObject.js @@ -735,6 +737,9 @@ Error: ${e}`); Object.assign(freshSaveData.stats, stats); loadGo(JSON.stringify(freshSaveData)); } + if (ver < 39) { + showAPIBreaks("2.6.1", ...breakInfos261); + } } async function loadGame(saveData: SaveData): Promise { diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 78e6ae940..6106b5926 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -3070,12 +3070,9 @@ export interface Bladeburner { * @remarks * RAM cost: 1 GB * - * Returns an object that represents the player’s current Bladeburner action. - * If the player is not performing an action, the function will return an object with the ‘type’ property set to “Idle”. - * - * @returns Object that represents the player’s current Bladeburner action. + * @returns Object that represents the player’s current Bladeburner action, or null if no action is being performed. */ - getCurrentAction(): BladeburnerCurAction; + getCurrentAction(): BladeburnerCurAction | null; /** * Get the time to complete an action. diff --git a/src/utils/APIBreaks/2.6.1.ts b/src/utils/APIBreaks/2.6.1.ts new file mode 100644 index 000000000..b49ef8b95 --- /dev/null +++ b/src/utils/APIBreaks/2.6.1.ts @@ -0,0 +1,29 @@ +import { APIBreakInfo } from "./APIBreak"; + +export const breakInfos261: APIBreakInfo[] = [ + { + brokenFunctions: ["ns.bladeburner.getCurrentAction"], + info: + "ns.bladeburner.getCurrentAction:\n" + + 'When not performing a bladeburner action, previously returned {type: "Idle", name: ""}, now returns null.\n' + + "Because of this change, the null case now needs to be dealt with prior to accessing properties on the return of getCurrentAction, including destructuring.\n" + + "Additionally, any existing code for filtering out the Idle case will need to be adjusted.\n\n" + + "See https://github.com/bitburner-official/bitburner-src/issues/1249 or PR https://github.com/bitburner-official/bitburner-src/pull/1248 for more details.", + }, + { + brokenFunctions: [ + "ns.bladeburner.getActionCountRemaining", + "ns.bladeburner.getActionEstimatedSuccessChance", + "ns.bladeburner.getActionTime", + ], + info: + "ns.bladeburner.getActionCountRemaining:\n" + + 'Previously returned -1 when called with type "Idle" and name "". This is no longer valid usage and will result in an error.\n\n' + + "ns.bladeburner.getActionEstimatedSuccessChance:\n" + + 'Previously returned [-1, -1] when called with type "Idle" and name "". This is no longer valid usage and will result in an error.\n\n' + + "ns.bladeburner.getActionTime:\n" + + 'Previously returned -1 when called with type "Idle" and name "". This is no longer valid usage and will result in an error.\n\n' + + "See the related changes for ns.bladeburner.getCurrentAction, which were shown earlier in these API break details.\n" + + "In most cases, the fixes for ns.bladeburner.getCurrentAction will fix this group of isses as well.", + }, +]; diff --git a/src/utils/APIBreaks/APIBreak.ts b/src/utils/APIBreaks/APIBreak.ts new file mode 100644 index 000000000..a627188c7 --- /dev/null +++ b/src/utils/APIBreaks/APIBreak.ts @@ -0,0 +1,91 @@ +// General reusable tools for API breaks + +import type { ScriptFilePath } from "../../Paths/ScriptFilePath"; +import type { Script } from "../../Script/Script"; +import { Player } from "@player"; +import { GetAllServers } from "../../Server/AllServers"; +import { resolveTextFilePath } from "../../Paths/TextFilePath"; +import { dialogBoxCreate as dialogBoxCreateOriginal } from "../../ui/React/DialogBox"; +import { Terminal } from "../../Terminal"; + +// Temporary until fixing alerts manager to store alerts outside of react scope +const dialogBoxCreate = (text: string) => setTimeout(() => dialogBoxCreateOriginal(text), 2000); + +/** For a single server, map of script filepath to an array of line numbers where impacted functions were detected */ +type ScriptImpactMap = Map; + +/** For an overall API break, map of server hostnames to an array of impacted scripts */ +type ImpactMap = Map; + +export interface APIBreakInfo { + /** The API functions impacted by the API break */ + brokenFunctions: string[]; + /** Info that should be shown to the player, alongside the list of impacted scripts */ + info: string; +} + +function getImpactedLines(script: Script, brokenFunctions: string[]): number[] | null { + const impactedLines: number[] = []; + script.content.split("\n").forEach((line, i) => { + for (const brokenFunction of brokenFunctions) { + if (line.includes(brokenFunction)) return impactedLines.push(i + 1); + } + }); + return impactedLines.length ? impactedLines : null; +} + +/** Returns a map keyed by all ser */ +function getImpactMap(brokenFunctions: string[]): ImpactMap | null { + const returnMap = new Map(); + for (const server of GetAllServers()) { + const impactedScripts = new Map(); + for (const [filename, script] of server.scripts) { + const impactedLines = getImpactedLines(script, brokenFunctions); + if (impactedLines) impactedScripts.set(filename, impactedLines); + } + if (impactedScripts.size) returnMap.set(server.hostname, impactedScripts); + } + return returnMap.size ? returnMap : null; +} + +/** Show the player a dialog for their API breaks, and save an info file for the player to review later */ +export function showAPIBreaks(version: string, ...breakInfos: APIBreakInfo[]) { + const details = []; + for (const breakInfo of breakInfos) { + const impactMap = getImpactMap(breakInfo.brokenFunctions); + if (!impactMap) continue; + details.push( + breakInfo.info + + `\n\nUsage of the following functions may have been affected:\n${breakInfo.brokenFunctions.join("\n")}\n\n` + + [...impactMap] + .map( + ([hostname, scriptImpactMap]) => + `Potentially affected files on server ${hostname} (with line numbers):\n` + + [...scriptImpactMap] + .map( + ([filename, lineNumbers]) => + `${filename}: (Line number${lineNumbers.length > 1 ? "s" : ""}: ${lineNumbers.join(", ")})`, + ) + .join("\n"), + ) + .join("\n\n"), + ); + } + if (!details.length) return; + const textFileName = resolveTextFilePath(`APIBreakInfo-${version}.txt`); + if (!textFileName) throw new Error("Version string created an invalid API break file name"); + Player.getHomeComputer().writeToTextFile( + textFileName, + `API BREAK INFO FOR ${version}\n\n${details.join("\n\n\n\n")}`, + ); + Terminal.warn(`AN API BREAK FROM VERSION ${version} MAY HAVE AFFECTED SOME OF YOUR SCRIPTS.`); + Terminal.warn(`INFORMATION ABOUT THIS POTENTIAL IMPACT HAS BEEN LOGGED IN ${textFileName} ON YOUR HOME COMPUTER.`); + dialogBoxCreate( + `SOME OF YOUR SCRIPTS HAVE POTENTIALLY BEEN IMPACTED BY AN API BREAK, DUE TO CHANGES IN VERSION ${version}\n\n` + + "The following dialog boxes will provide details of the potential impact to your scripts.\n" + + `A file with these details has also been saved on your home computer under filename ${textFileName}.`, + ); + details.forEach((detail, i) => { + dialogBoxCreate(`API BREAK VERSION ${version} DETAILS ${i + 1} of ${details.length}\n\n${detail}`); + }); +}