diff --git a/src/Constants.ts b/src/Constants.ts index bc6660f46..b71a1ec8e 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -270,6 +270,24 @@ v2.0.0 - 2022-07-19 Work rework are very common tokens. * corporation.bribe no longer allows to give shares as bribe. + Netscript + + * Add getAugmentationBasePrice + * Add getSleeveAugmentationPrice + * Add getSleeveAugmentationRepReq + * Fix getInfiltrationLocations + * Singularity.goToLocation support for non-city-specific locations (@Ansopedian) + * All corporation functions are synchronous. Job assignment only works on the following cycle. (@stalefishies) + * Add batch functionality to NS spendHashes API (@undeemiss) + * Fix #3661 Add missing memory property to Sleeve API (@borisflagell) + * FIX#3732 Cannot assign two sleeve on "Take on contracts" regardless of contract type. (@borisflagell) + + Corporation + + * Dividend fixes and exposing dividends info via scripts (@stalefishies) + * Add big number format support in some Corporation's modal (@borisflagell) + * Fix #3261 Industry overview number formatting (@nickofolas) + Multipliers * The main player object was also plagues with a million fields all called '*_mult'. Representing the different multipliers @@ -277,6 +295,15 @@ v2.0.0 - 2022-07-19 Work rework Misc. + * #3596 Enhanced terminal command parsing (@RevanProdigalKnight) + * Fix #3366 Sleeve UI would sometimes displays the wrong stat while working out. (@borisflagell) + * Two new encryption themed contracts - caesar and vigenere (@Markus-D-M) + * Fixes #3132 several Sleeve can no longer works concurrently in the same company (@borisflagell) + * FIX #3514 Clear recently killed tab on BN end event (@Daniel-Barbera) + * HammingCodes description and implementation fixes (@s2ks) + * FIX #3794 Sleeve were getting less shocked when hospitalized (was positive, should have detrimental) (@borisflagell) + * Fix #3803 Servers can no longer have duplicate IPs (@crimsonhawk47) + * Fix #3854 ctrl+c does not clear terminal input (@evil-tim) * Nerf noodle bar, obviously. `, diff --git a/src/SaveObject.tsx b/src/SaveObject.tsx index df9aa9231..765b5ba55 100755 --- a/src/SaveObject.tsx +++ b/src/SaveObject.tsx @@ -35,6 +35,7 @@ import { FactionNames } from "./Faction/data/FactionNames"; import { Faction } from "./Faction/Faction"; import { safetlyCreateUniqueServer } from "./Server/ServerHelpers"; import { SpecialServers } from "./Server/data/SpecialServers"; +import { v2APIBreak } from "./utils/v2APIBreak"; /* SaveObject.js * Defines the object used to save/load games @@ -461,6 +462,7 @@ function evaluateVersionCompatibility(ver: string | number): void { if (create) Player.getHomeComputer().pushProgram(create); const graft = anyPlayer["graftAugmentationName"]; if (graft) Player.augmentations.push({ name: graft, level: 1 }); + v2APIBreak(); } } } diff --git a/src/engine.tsx b/src/engine.tsx index ecb280a7a..3748be96f 100644 --- a/src/engine.tsx +++ b/src/engine.tsx @@ -291,7 +291,9 @@ const Engine: { const offlineHackingIncome = (Player.moneySourceA.hacking / Player.playtimeSinceLastAug) * timeOffline * 0.75; Player.gainMoney(offlineHackingIncome, "hacking"); // Process offline progress + loadAllRunningScripts(Player); // This also takes care of offline production for those scripts + if (Player.currentWork !== null) { Player.focus = true; Player.processWork(numCyclesOffline); diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 293038414..281ef5006 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -87,6 +87,7 @@ import _wrap from "lodash/wrap"; import _functions from "lodash/functions"; import { Apr1 } from "./Apr1"; import { isFactionWork } from "../Work/FactionWork"; +import { V2Modal } from "../utils/V2Modal"; const htmlLocation = location; @@ -558,6 +559,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme + ); diff --git a/src/utils/V2Modal.tsx b/src/utils/V2Modal.tsx new file mode 100644 index 000000000..f3dc4b6de --- /dev/null +++ b/src/utils/V2Modal.tsx @@ -0,0 +1,35 @@ +import { Button, Typography } from "@mui/material"; +import React, { useState } from "react"; +import { Modal } from "../ui/React/Modal"; + +let v2ModalOpen = false; + +export const openV2Modal = (): void => { + v2ModalOpen = true; +}; + +export const V2Modal = (): React.ReactElement => { + const [open, setOpen] = useState(v2ModalOpen); + return ( + undefined}> + Welcome to bitburner v2.0.0!{" "} + While this version does not change the game a lot, it does have quite a few API breaks.{" "} + + A file was added to your home computer called V2_0_0_API_BREAK.txt and it is highly recommended you take a look + at this file. It explains where most of the API break have occured. + {" "} + + You should also take a look at{" "} + + {" "} + the migration guide + {" "} + as well as{" "} + + the changelog + + + + + ); +}; diff --git a/src/utils/v1APIBreak.ts b/src/utils/v1APIBreak.ts index bdbdffea3..e9f3bde1f 100644 --- a/src/utils/v1APIBreak.ts +++ b/src/utils/v1APIBreak.ts @@ -84,11 +84,13 @@ export function AwardNFG(n = 1): void { } } +export interface IFileLine { + file: string; + line: number; + content: string; +} + export function v1APIBreak(): void { - interface IFileLine { - file: string; - line: number; - } let txt = ""; for (const server of GetAllServers()) { for (const change of detect) { @@ -100,6 +102,7 @@ export function v1APIBreak(): void { s.push({ file: script.filename, line: i + 1, + content: "", }); } } diff --git a/src/utils/v2APIBreak.ts b/src/utils/v2APIBreak.ts index 5bcef5dcb..19f36714e 100644 --- a/src/utils/v2APIBreak.ts +++ b/src/utils/v2APIBreak.ts @@ -1,4 +1,10 @@ -export const singularity = [ +import { saveObject } from "../SaveObject"; +import { Script } from "../Script/Script"; +import { GetAllServers, GetServer } from "../Server/AllServers"; +import { IFileLine } from "./v1APIBreak"; +import { openV2Modal } from "./V2Modal"; + +const singularity = [ "applyToCompany", "b1tflum3", "checkFactionInvitations", @@ -52,3 +58,197 @@ export const singularity = [ "workForCompany", "workForFaction", ]; + +const getPlayerFields = [ + "workChaExpGained", + "currentWorkFactionName", + "workDexExpGained", + "workHackExpGained", + "createProgramReqLvl", + "workStrExpGained", + "companyName", + "crimeType", + "workRepGained", + "workChaExpGainRate", + "workType", + "workStrExpGainRate", + "isWorking", + "workRepGainRate", + "workDefExpGained", + "currentWorkFactionDescription", + "workHackExpGainRate", + "workAgiExpGainRate", + "workDexExpGainRate", + "workMoneyGained", + "workMoneyLossRate", + "workMoneyGainRate", + "createProgramName", + "workDefExpGainRate", + "workAgiExpGained", + "className", +]; + +const mults = [ + "hacking_chance_mult", + "hacking_speed_mult", + "hacking_money_mult", + "hacking_grow_mult", + "hacking_mult", + "hacking_exp_mult", + "strength_mult", + "strength_exp_mult", + "defense_mult", + "defense_exp_mult", + "dexterity_mult", + "dexterity_exp_mult", + "agility_mult", + "agility_exp_mult", + "charisma_mult", + "charisma_exp_mult", + "hacknet_node_money_mult", + "hacknet_node_purchase_cost_mult", + "hacknet_node_ram_cost_mult", + "hacknet_node_core_cost_mult", + "hacknet_node_level_cost_mult", + "company_rep_mult", + "faction_rep_mult", + "work_money_mult", + "crime_success_mult", + "crime_money_mult", + "bladeburner_max_stamina_mult", + "bladeburner_stamina_gain_mult", + "bladeburner_analysis_mult", + "bladeburner_success_chance_mult", +]; + +interface IRule { + match: RegExp; + reason: string; + offenders: IFileLine[]; +} + +export const v2APIBreak = () => { + const home = GetServer("home"); + if (!home) throw new Error("'home' server was not found."); + const rules: IRule[] = [ + { + match: /ns\.workForCompany/g, + reason: "workForCompany argument companyName is now not-optional.", + offenders: [], + }, + { + match: /ns\.getScriptExpGain/g, + reason: "getScriptExpGain with 0 argument no longer returns the sum of all scripts. Use getTotalScriptExpGain", + offenders: [], + }, + { + match: /ns\.getScriptExpGain/g, + reason: "getScriptIncome with 0 argument no longer returns the sum of all scripts. Use getTotalScriptIncome", + offenders: [], + }, + { + match: /ns\.scp/g, + reason: + "scp arguments were switch, it is now scp(files, destination, optionally_source). If you were using 2 argument (not 3) this doesn't affect you.", + offenders: [], + }, + { + match: /ns\.stock\.buy/g, + reason: "buy is a very common word so in order to avoid ram costs it was renamed ns.stock.buyStock", + offenders: [], + }, + { + match: /ns\.stock\.sell/g, + reason: "sell is a very common word so in order to avoid ram costs it was renamed ns.stock.sellStock", + offenders: [], + }, + { + match: /ns\.corporation\.bribe/g, + reason: "bribe no longer allows you to give shares of the corporation, only money", + offenders: [], + }, + ]; + + for (const fn of singularity) { + rules.push({ + match: new RegExp(`ns.${fn}`, "g"), + reason: `ns.${fn} was moved to ns.singularity.${fn}`, + offenders: [], + }); + } + + for (const mult of mults) { + rules.push({ + match: new RegExp(mult, "g"), + reason: `ns.getPlayer().${mult} was moved to ns.getPlayer().mults.${mult.slice(0, mult.length - 5)}`, + offenders: [], + }); + } + + for (const f of getPlayerFields) { + rules.push({ + match: new RegExp(f, "g"), + reason: `The work system is completely reworked and ns.getPlayer().${f} no longer exists. This data is likely available inside ns.getPlayer().currentWork`, + offenders: [], + }); + } + + for (const script of home.scripts) { + processScript(rules, script); + } + + home.writeToTextFile("V2_0_0_API_BREAK.txt", formatRules(rules)); + openV2Modal(); + + for (const server of GetAllServers()) { + server.runningScripts = []; + } + saveObject.exportGame(); +}; + +const formatOffenders = (offenders: IFileLine[]): string => { + const files: Record = {}; + for (const off of offenders) { + const current = files[off.file] ?? []; + current.push(off); + files[off.file] = current; + } + + let txt = ""; + for (const file in files) { + txt += "\t" + file + "\n"; + for (const fileline of files[file]) { + txt += `\t\tLine ${fileline.line} ${fileline.content.trim()}\n`; + } + } + return txt; +}; + +const formatRules = (rules: IRule[]): string => { + let txt = + "This file contains the list of potential API break. A pattern was used to look through all your files and note the spots where you might have a problem. Not everything here is broken."; + for (const rule of rules) { + if (rule.offenders.length === 0) continue; + txt += String(rule.match) + "\n"; + txt += rule.reason + "\n\n"; + txt += formatOffenders(rule.offenders); + txt += "\n\n"; + } + return txt; +}; + +const processScript = (rules: IRule[], script: Script) => { + const lines = script.code.split("\n"); + for (let i = 0; i < lines.length; i++) { + for (const rule of rules) { + const line = lines[i]; + if (line.match(rule.match)) { + rule.offenders.push({ + file: script.filename, + line: i + 1, + content: line, + }); + } + } + } +};