diff --git a/src/utils/APIBreaks/2.6.1.ts b/src/utils/APIBreaks/2.6.1.ts index 3a580d1e7..061875612 100644 --- a/src/utils/APIBreaks/2.6.1.ts +++ b/src/utils/APIBreaks/2.6.1.ts @@ -1,31 +1,33 @@ -import { APIBreakInfo } from "./APIBreak"; +import type { VersionBreakingChange } from "./APIBreak"; -export const breakInfos261: APIBreakInfo[] = [ - { - brokenAPIs: [{ name: "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.", - showPopUp: true, - }, - { - brokenAPIs: [ - { name: "ns.bladeburner.getActionCountRemaining" }, - { name: "ns.bladeburner.getActionEstimatedSuccessChance" }, - { name: "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 issues as well.", - showPopUp: true, - }, -]; +export const breakInfos261: VersionBreakingChange = { + apiBreakingChanges: [ + { + brokenAPIs: [{ name: "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.", + showPopUp: true, + }, + { + brokenAPIs: [ + { name: "ns.bladeburner.getActionCountRemaining" }, + { name: "ns.bladeburner.getActionEstimatedSuccessChance" }, + { name: "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 issues as well.", + showPopUp: true, + }, + ], +}; diff --git a/src/utils/APIBreaks/3.0.0.ts b/src/utils/APIBreaks/3.0.0.ts index b0354ce5c..aa27169c0 100644 --- a/src/utils/APIBreaks/3.0.0.ts +++ b/src/utils/APIBreaks/3.0.0.ts @@ -1,112 +1,133 @@ -import { APIBreakInfo } from "./APIBreak"; +import type { VersionBreakingChange } from "./APIBreak"; -export const breakInfos300: APIBreakInfo[] = [ - { - brokenAPIs: [{ name: "ns.nFormat" }], - info: - "ns.nFormat() was removed.\n" + - "Use ns.formatNumber, ns.formatRam, ns.formatPercent, or JS built-in objects/functions (e.g., Intl.NumberFormat, " + - "Intl.PluralRules, Intl.Locale) instead.", - showPopUp: true, - }, - { - brokenAPIs: [ - { - name: "ns.getTimeSinceLastAug", - migration: { - searchValue: "ns.getTimeSinceLastAug()", - replaceValue: "(Date.now() - ns.getResetInfo().lastAugReset)", +export const breakingChanges300: VersionBreakingChange = { + additionalText: "For more information, please check https://github.com/bitburner-official/bitburner-src/issues/2148.", + apiBreakingChanges: [ + { + brokenAPIs: [{ name: "ns.nFormat" }], + info: + "ns.nFormat() was removed.\n" + + "Use ns.formatNumber, ns.formatRam, ns.formatPercent, or JS built-in objects/functions (e.g., Intl.NumberFormat, " + + "Intl.PluralRules, Intl.Locale) instead.", + showPopUp: true, + }, + { + brokenAPIs: [ + { + name: "ns.getTimeSinceLastAug", + migration: { + searchValue: "ns.getTimeSinceLastAug()", + replaceValue: "(Date.now() - ns.getResetInfo().lastAugReset)", + }, }, - }, - { - name: "ns.getPlayer().playtimeSinceLastAug", - migration: { - searchValue: "ns.getPlayer().playtimeSinceLastAug", - replaceValue: "(Date.now() - ns.getResetInfo().lastAugReset)", + { + name: "ns.getPlayer().playtimeSinceLastAug", + migration: { + searchValue: "ns.getPlayer().playtimeSinceLastAug", + replaceValue: "(Date.now() - ns.getResetInfo().lastAugReset)", + }, }, - }, - { - name: "ns.getPlayer().playtimeSinceLastBitnode", - migration: { - searchValue: "ns.getPlayer().playtimeSinceLastBitnode", - replaceValue: "(Date.now() - ns.getResetInfo().lastNodeReset)", + { + name: "ns.getPlayer().playtimeSinceLastBitnode", + migration: { + searchValue: "ns.getPlayer().playtimeSinceLastBitnode", + replaceValue: "(Date.now() - ns.getResetInfo().lastNodeReset)", + }, }, - }, - { - name: "ns.getPlayer().bitNodeN", - migration: { - searchValue: "ns.getPlayer().bitNodeN", - replaceValue: "ns.getResetInfo().currentNode", + { + name: "ns.getPlayer().bitNodeN", + migration: { + searchValue: "ns.getPlayer().bitNodeN", + replaceValue: "ns.getResetInfo().currentNode", + }, }, - }, - { - name: "ns.corporation.getCorporation().state", - migration: { - searchValue: "ns.corporation.getCorporation().state", - replaceValue: "ns.corporation.getCorporation().nextState", + { + name: "ns.corporation.getCorporation().state", + migration: { + searchValue: "ns.corporation.getCorporation().state", + replaceValue: "ns.corporation.getCorporation().nextState", + }, }, - }, - ], - info: - "ns.getTimeSinceLastAug was removed.\n" + - 'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastAugReset".\n\n' + - "ns.getPlayer().playtimeSinceLastAug was removed.\n" + - 'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastAugReset".\n\n' + - "ns.getPlayer().playtimeSinceLastBitnode was removed.\n" + - 'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastNodeReset".\n\n' + - "ns.getPlayer().bitNodeN was removed.\n" + - 'It has been automatically replaced with "ns.getResetInfo().currentNode"\n\n' + - "ns.corporation.getCorporation().state was removed.\n" + - 'It has been automatically replaced with "ns.corporation.getCorporation().nextState"', - showPopUp: false, - }, - { - brokenAPIs: [ - { name: "ns.formatNumber", migration: { searchValue: "ns.formatNumber", replaceValue: "ns.format.number" } }, - { name: "ns.formatRam", migration: { searchValue: "ns.formatRam", replaceValue: "ns.format.ram" } }, - { name: "ns.formatPercent", migration: { searchValue: "ns.formatPercent", replaceValue: "ns.format.percent" } }, - { name: "ns.tFormat", migration: { searchValue: "ns.tFormat", replaceValue: "ns.format.time" } }, - ], - info: - "The formatting functions have been moved to their own interface, ns.format.\n" + - "Each function has been replaced with their corresponding interface variant.\n" + - "Additionally, the naming of ns.tFormat has been changed to ns.format.time.", - showPopUp: false, - }, - { - brokenAPIs: [ - { name: "ns.tail", migration: { searchValue: "ns.tail", replaceValue: "ns.ui.openTail" } }, - { name: "ns.moveTail", migration: { searchValue: "ns.moveTail", replaceValue: "ns.ui.moveTail" } }, - { name: "ns.resizeTail", migration: { searchValue: "ns.resizeTail", replaceValue: "ns.ui.resizeTail" } }, - { name: "ns.closeTail", migration: { searchValue: "ns.closeTail", replaceValue: "ns.ui.closeTail" } }, - { name: "ns.setTitle", migration: { searchValue: "ns.setTitle", replaceValue: "ns.ui.setTailTitle" } }, - ], - info: - "ns.tail() was removed.\n" + - 'It has been automatically replaced with "ns.ui.openTail()".\n\n' + - "ns.moveTail() was removed.\n" + - 'It has been automatically replaced with "ns.ui.moveTail()".\n\n' + - "ns.resizeTail() was removed.\n" + - 'It has been automatically replaced with "ns.ui.resizeTail()".\n\n' + - "ns.closeTail() was removed.\n" + - 'It has been automatically replaced with "ns.ui.closeTail()".\n\n' + - "ns.setTitle() was removed.\n" + - 'It has been automatically replaced with "ns.ui.setTailTitle()".\n\n', - showPopUp: false, - }, - { - brokenAPIs: [ - { - name: "ns.corporation.setAutoJobAssignment", - migration: { - searchValue: "setAutoJobAssignment", - replaceValue: "setJobAssignment", + ], + info: + "ns.getTimeSinceLastAug was removed.\n" + + 'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastAugReset".\n\n' + + "ns.getPlayer().playtimeSinceLastAug was removed.\n" + + 'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastAugReset".\n\n' + + "ns.getPlayer().playtimeSinceLastBitnode was removed.\n" + + 'It has been automatically replaced with "Date.now() - ns.getResetInfo().lastNodeReset".\n\n' + + "ns.getPlayer().bitNodeN was removed.\n" + + 'It has been automatically replaced with "ns.getResetInfo().currentNode"\n\n' + + "ns.corporation.getCorporation().state was removed.\n" + + 'It has been automatically replaced with "ns.corporation.getCorporation().nextState"', + showPopUp: false, + }, + { + brokenAPIs: [ + { name: "ns.formatNumber", migration: { searchValue: "ns.formatNumber", replaceValue: "ns.format.number" } }, + { name: "ns.formatRam", migration: { searchValue: "ns.formatRam", replaceValue: "ns.format.ram" } }, + { name: "ns.formatPercent", migration: { searchValue: "ns.formatPercent", replaceValue: "ns.format.percent" } }, + { name: "ns.tFormat", migration: { searchValue: "ns.tFormat", replaceValue: "ns.format.time" } }, + ], + info: + "The formatting functions have been moved to their own interface, ns.format.\n" + + "Each function has been replaced with their corresponding interface variant.\n" + + "Additionally, the naming of ns.tFormat has been changed to ns.format.time.", + showPopUp: false, + }, + { + brokenAPIs: [ + { name: "ns.tail", migration: { searchValue: "ns.tail", replaceValue: "ns.ui.openTail" } }, + { name: "ns.moveTail", migration: { searchValue: "ns.moveTail", replaceValue: "ns.ui.moveTail" } }, + { name: "ns.resizeTail", migration: { searchValue: "ns.resizeTail", replaceValue: "ns.ui.resizeTail" } }, + { name: "ns.closeTail", migration: { searchValue: "ns.closeTail", replaceValue: "ns.ui.closeTail" } }, + { name: "ns.setTitle", migration: { searchValue: "ns.setTitle", replaceValue: "ns.ui.setTailTitle" } }, + ], + info: + "ns.tail() was removed.\n" + + 'It has been automatically replaced with "ns.ui.openTail()".\n\n' + + "ns.moveTail() was removed.\n" + + 'It has been automatically replaced with "ns.ui.moveTail()".\n\n' + + "ns.resizeTail() was removed.\n" + + 'It has been automatically replaced with "ns.ui.resizeTail()".\n\n' + + "ns.closeTail() was removed.\n" + + 'It has been automatically replaced with "ns.ui.closeTail()".\n\n' + + "ns.setTitle() was removed.\n" + + 'It has been automatically replaced with "ns.ui.setTailTitle()".\n\n', + showPopUp: false, + }, + { + brokenAPIs: [ + { + name: "ns.corporation.setAutoJobAssignment", + migration: { + searchValue: "setAutoJobAssignment", + replaceValue: "setJobAssignment", + }, }, - }, - ], - info: - "ns.corporation.setAutoJobAssignment() was removed.\n" + - 'It has been automatically replaced with "ns.corporation.setJobAssignment()".\n\n', - showPopUp: false, - }, -]; + ], + info: + "ns.corporation.setAutoJobAssignment() was removed.\n" + + 'It has been automatically replaced with "ns.corporation.setJobAssignment()".\n\n', + showPopUp: false, + }, + { + brokenAPIs: [], + info: + "With some APIs, when you passed values to their params, you could pass a value that was not an exact match. " + + 'For example, with "ns.singularity.commitCrime", you could pass "Rob Store", "rob store", "RobStore", "robstore", "robStore", etc. ' + + 'This is called "fuzzy matching". Now, you must pass an exact value (i.e., Rob Store). This change affects:\n' + + "- Bladeburner action and type: BladeburnerActionType, BladeburnerGeneralActionName, BladeburnerContractName, BladeburnerOperationName, BladeburnerBlackOpName, SpecialBladeburnerActionTypeForSleeve, BladeburnerActionTypeForSleeve.\n" + + "- Crime: CrimeType\n" + + "- Faction work: FactionWorkType\n" + + "- University class: UniversityClassType\n" + + "- Gym stat: GymType\n" + + "- Job field: JobField\n" + + "- Stock position: PositionType\n" + + "- Stock order: OrderType\n" + + "You can access these values via ns.enums and Bladeburner APIs.", + showPopUp: true, + doNotSkip: true, + }, + ], +}; diff --git a/src/utils/APIBreaks/APIBreak.ts b/src/utils/APIBreaks/APIBreak.ts index 640b93f2e..96b3953d2 100644 --- a/src/utils/APIBreaks/APIBreak.ts +++ b/src/utils/APIBreaks/APIBreak.ts @@ -19,6 +19,11 @@ type ScriptImpactMap = Map; /** For an overall API break, map of server hostnames to an array of impacted scripts */ type ImpactMap = Map; +export interface VersionBreakingChange { + additionalText?: string; + apiBreakingChanges: APIBreakInfo[]; +} + export interface APIBreakInfo { /** The API functions impacted by the API break */ brokenAPIs: { @@ -33,6 +38,19 @@ export interface APIBreakInfo { info: string; /** If broken APIs can be safely migrated, we can skip displaying the notification popup */ showPopUp: boolean; + /** + * With a new version with breaking changes, the "showAPIBreaks" function checks all breaking changes and does 2 + * things with changes that affect the player's scripts: + * - Write info of changes to a log file. + * - Show popups per change. + * Note that we skip changes that do not affect the player's scripts. This is problematic with some breaking changes. + * + * With each breaking change in "brokenAPIs", we try to detect the affected code by using "name" or + * "migration.searchValue". However, with some breaking changes, we cannot detect the affected code reliably via + * "brokenAPIs". In this case, instead of skipping them, we always "process" that change (i.e., write info to the log + * file and optionally show a popup that notifies the player about this change). + */ + doNotSkip?: boolean; } function detectImpactAndMigrateLines(script: Script, brokenFunctions: APIBreakInfo["brokenAPIs"]): number[] | null { @@ -54,7 +72,7 @@ function detectImpactAndMigrateLines(script: Script, brokenFunctions: APIBreakIn } /** Returns a map keyed by hostname */ -function detectImpactAndMigrate(brokenFunctions: APIBreakInfo["brokenAPIs"]): ImpactMap | null { +function detectImpactAndMigrate(brokenFunctions: APIBreakInfo["brokenAPIs"]): ImpactMap { const returnMap = new Map(); for (const server of GetAllServers()) { const impactedScripts = new Map(); @@ -68,24 +86,25 @@ function detectImpactAndMigrate(brokenFunctions: APIBreakInfo["brokenAPIs"]): Im returnMap.set(server.hostname, impactedScripts); } } - return returnMap.size ? returnMap : null; + return returnMap; } /** 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[]) { +export function showAPIBreaks(version: string, { additionalText, apiBreakingChanges }: VersionBreakingChange) { const details: { text: string; showPopUp: boolean; }[] = []; let numberOfPopUps = 0; - for (const breakInfo of breakInfos) { + for (const breakInfo of apiBreakingChanges) { const impactMap = detectImpactAndMigrate(breakInfo.brokenAPIs); - if (!impactMap) { + // Skip processing if we don't find any affected code and the breaking change does not enable the "doNotSkip" flag. + if (impactMap.size === 0 && !breakInfo.doNotSkip) { continue; } - details.push({ - text: - breakInfo.info + + let detailText = breakInfo.info; + if (impactMap.size > 0) { + detailText += `\n\nUsage of the following functions may have been affected:\n${breakInfo.brokenAPIs .map((func) => func.name) .join("\n")}\n\n` + @@ -102,7 +121,10 @@ export function showAPIBreaks(version: string, ...breakInfos: APIBreakInfo[]) { ) .join("\n"), ) - .join("\n\n"), + .join("\n\n"); + } + details.push({ + text: detailText, showPopUp: breakInfo.showPopUp, }); if (breakInfo.showPopUp) { @@ -125,7 +147,8 @@ export function showAPIBreaks(version: string, ...breakInfos: APIBreakInfo[]) { 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}.`, + `A file with these details has also been saved on your home computer under filename ${textFileName}.` + + (additionalText ? `\n\n${additionalText}` : ""), ); let popUpIndex = 0; for (const detail of details) { diff --git a/src/utils/SaveDataMigrationUtils.ts b/src/utils/SaveDataMigrationUtils.ts index e12eab109..05d66bdb7 100644 --- a/src/utils/SaveDataMigrationUtils.ts +++ b/src/utils/SaveDataMigrationUtils.ts @@ -30,7 +30,7 @@ import { exportMaterial } from "../Corporation/Actions"; import { getGoSave, loadGo } from "../Go/SaveLoad"; import { showAPIBreaks } from "./APIBreaks/APIBreak"; import { breakInfos261 } from "./APIBreaks/2.6.1"; -import { breakInfos300 } from "./APIBreaks/3.0.0"; +import { breakingChanges300 } from "./APIBreaks/3.0.0"; /** Function for performing a series of defined replacements. See 0.58.0 for usage */ function convert(code: string, changes: [RegExp, string][]): string { @@ -527,7 +527,7 @@ Error: ${e}`, loadGo(JSON.stringify(freshSaveData)); } if (ver < 39) { - showAPIBreaks("2.6.1", ...breakInfos261); + showAPIBreaks("2.6.1", breakInfos261); } if (ver < 42) { // All whitespace except for spaces was allowed in filenames @@ -547,6 +547,6 @@ Error: ${e}`, if (found) Terminal.error("Filenames with whitespace found and corrected, see console for details."); } if (ver < 44) { - showAPIBreaks("3.0.0", ...breakInfos300); + showAPIBreaks("3.0.0", breakingChanges300); } }