diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 8840ea1b8..154295e4b 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -1,8 +1,13 @@ // Defined by webpack on startup or compilation -declare let __COMMIT_HASH__: string; +declare const __COMMIT_HASH__: string; // When using file-loader, we'll get a path to the resource declare module "*.png" { const value: string; export default value; } + +// Achievements communicated back to Electron shell for Steam. +declare interface Document { + achievements: string[]; +} diff --git a/src/Achievements/Achievements.ts b/src/Achievements/Achievements.ts index c8b56065b..69cf75266 100644 --- a/src/Achievements/Achievements.ts +++ b/src/Achievements/Achievements.ts @@ -799,5 +799,5 @@ export function calculateAchievements(): void { // Write all player's achievements to document for Steam/Electron // This could be replaced by "availableAchievements" // if we don't want to grant the save game achievements to steam but only currently available - (document as any).achievements = [...Player.achievements.map((a) => a.ID)]; + document.achievements = [...Player.achievements.map((a) => a.ID)]; } diff --git a/src/Augmentation/data/AugmentationCreator.tsx b/src/Augmentation/data/AugmentationCreator.tsx index 02ce0bdd2..6c3519df5 100644 --- a/src/Augmentation/data/AugmentationCreator.tsx +++ b/src/Augmentation/data/AugmentationCreator.tsx @@ -109,6 +109,7 @@ export const initSoAAugmentations = (): Augmentation[] => [ rewards, reduced damage taken, etc. ), + isSpecial: true, factions: [FactionNames.ShadowsOfAnarchy], }), new Augmentation({ @@ -121,6 +122,7 @@ export const initSoAAugmentations = (): Augmentation[] => [ stats: ( <>This augmentation makes the Slash minigame easier by showing you via an indictor when the slash in coming. ), + isSpecial: true, factions: [FactionNames.ShadowsOfAnarchy], }), new Augmentation({ @@ -129,6 +131,7 @@ export const initSoAAugmentations = (): Augmentation[] => [ moneyCost: 1e6, info: "A connective brain implant to SASHA that focuses in pattern recognition and predictive templating.", stats: <>This augmentation makes the Bracket minigame easier by removing all '[' ']'., + isSpecial: true, factions: [FactionNames.ShadowsOfAnarchy], }), new Augmentation({ @@ -137,6 +140,7 @@ export const initSoAAugmentations = (): Augmentation[] => [ moneyCost: 1e6, info: "Opto-occipito implant to process visual signal before brain interpretation.", stats: <>This augmentation makes the Backwards minigame easier by flipping the words., + isSpecial: true, factions: [FactionNames.ShadowsOfAnarchy], }), new Augmentation({ @@ -147,6 +151,7 @@ export const initSoAAugmentations = (): Augmentation[] => [ "Pheromone extruder injected in the thoracodorsal nerve. Emits pleasing scent guaranteed to " + "make conversational partners more agreeable.", stats: <>This augmentation makes the Bribe minigame easier by indicating the incorrect paths., + isSpecial: true, factions: [FactionNames.ShadowsOfAnarchy], }), new Augmentation({ @@ -155,6 +160,7 @@ export const initSoAAugmentations = (): Augmentation[] => [ moneyCost: 1e6, info: "Penta-dynamo-neurovascular-valve inserted in the carpal ligament, enhances dexterity.", stats: <>This augmentation makes the Cheat Code minigame easier by allowing the opposite character., + isSpecial: true, factions: [FactionNames.ShadowsOfAnarchy], }), new Augmentation({ @@ -163,6 +169,7 @@ export const initSoAAugmentations = (): Augmentation[] => [ moneyCost: 1e6, info: "Transtinatium VVD reticulator used in optico-sterbing recognition.", stats: <>This augmentation makes the Symbol matching minigame easier by indicating the correct choice., + isSpecial: true, factions: [FactionNames.ShadowsOfAnarchy], }), new Augmentation({ @@ -176,6 +183,7 @@ export const initSoAAugmentations = (): Augmentation[] => [ position. ), + isSpecial: true, factions: [FactionNames.ShadowsOfAnarchy], }), new Augmentation({ @@ -184,6 +192,7 @@ export const initSoAAugmentations = (): Augmentation[] => [ moneyCost: 1e6, info: "Neodynic retention fjengeln spoofer using -φ karmions, net positive effect on implantees delta wave.", stats: <>This augmentation makes the Wire Cutting minigame easier by indicating the incorrect wires., + isSpecial: true, factions: [FactionNames.ShadowsOfAnarchy], }), ]; @@ -242,7 +251,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [ moneyCost: 1.15e8, repCost: 2.75e4, info: "The latest version of the 'Augmented Targeting' implant adds the ability to lock-on and track threats.", - prereqs: [AugmentationNames.Targeting2], + prereqs: [AugmentationNames.Targeting2, AugmentationNames.Targeting1], dexterity_mult: 1.3, factions: [ FactionNames.TheDarkArmy, @@ -339,7 +348,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [ info: "The latest version of the 'Combat Rib' augmentation releases advanced anabolic steroids that " + "improve muscle mass and physical performance while being safe and free of side effects.", - prereqs: [AugmentationNames.CombatRib2], + prereqs: [AugmentationNames.CombatRib2, AugmentationNames.CombatRib1], strength_mult: 1.18, defense_mult: 1.18, factions: [ @@ -673,7 +682,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [ "This upgraded firmware allows the Embedded Netburner Module to control information on " + "a network by re-routing traffic, spoofing IP addresses, and altering the data inside network " + "packets.", - prereqs: [AugmentationNames.ENMCore], + prereqs: [AugmentationNames.ENMCore, AugmentationNames.ENM], hacking_speed_mult: 1.05, hacking_money_mult: 1.3, hacking_chance_mult: 1.05, @@ -698,7 +707,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [ "The Core V3 library is an implant that upgrades the firmware of the Embedded Netburner Module. " + "This upgraded firmware allows the Embedded Netburner Module to seamlessly inject code into " + "any device on a network.", - prereqs: [AugmentationNames.ENMCoreV2], + prereqs: [AugmentationNames.ENMCoreV2, AugmentationNames.ENMCore, AugmentationNames.ENM], hacking_speed_mult: 1.05, hacking_money_mult: 1.4, hacking_chance_mult: 1.1, @@ -826,7 +835,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [ "are a set of specialized microprocessors that are attached to " + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + "so that the brain doesn't have to.", - prereqs: [AugmentationNames.CranialSignalProcessorsG2], + prereqs: [AugmentationNames.CranialSignalProcessorsG2, AugmentationNames.CranialSignalProcessorsG1], hacking_speed_mult: 1.02, hacking_money_mult: 1.15, hacking_mult: 1.09, @@ -841,7 +850,11 @@ export const initGeneralAugmentations = (): Augmentation[] => [ "are a set of specialized microprocessors that are attached to " + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + "so that the brain doesn't have to.", - prereqs: [AugmentationNames.CranialSignalProcessorsG3], + prereqs: [ + AugmentationNames.CranialSignalProcessorsG3, + AugmentationNames.CranialSignalProcessorsG2, + AugmentationNames.CranialSignalProcessorsG1, + ], hacking_speed_mult: 1.02, hacking_money_mult: 1.2, hacking_grow_mult: 1.25, @@ -856,7 +869,12 @@ export const initGeneralAugmentations = (): Augmentation[] => [ "are a set of specialized microprocessors that are attached to " + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + "so that the brain doesn't have to.", - prereqs: [AugmentationNames.CranialSignalProcessorsG4], + prereqs: [ + AugmentationNames.CranialSignalProcessorsG4, + AugmentationNames.CranialSignalProcessorsG3, + AugmentationNames.CranialSignalProcessorsG2, + AugmentationNames.CranialSignalProcessorsG1, + ], hacking_mult: 1.3, hacking_money_mult: 1.25, hacking_grow_mult: 1.75, @@ -1254,6 +1272,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [ moneyCost: 0, info: "It's time to leave the cave.", stats: null, + isSpecial: true, factions: [FactionNames.Daedalus], }), new Augmentation({ @@ -1952,7 +1971,7 @@ export const initChurchOfTheMachineGodAugmentations = (): Augmentation[] => [ "You will become greater than the sum of our parts. As One. Embrace your gift " + "fully and wholly free of it's accursed toll. Serenity brings tranquility the form " + "of no longer suffering a stat penalty. ", - prereqs: [AugmentationNames.StaneksGift2], + prereqs: [AugmentationNames.StaneksGift2, AugmentationNames.StaneksGift1], isSpecial: true, hacking_chance_mult: 1 / 0.95, hacking_speed_mult: 1 / 0.95, @@ -2003,6 +2022,7 @@ export function initNeuroFluxGovernor(): Augmentation { multiplicatively. ), + isSpecial: true, hacking_chance_mult: 1.01 + donationBonus, hacking_speed_mult: 1.01 + donationBonus, hacking_money_mult: 1.01 + donationBonus, diff --git a/src/Augmentation/ui/PurchasableAugmentations.tsx b/src/Augmentation/ui/PurchasableAugmentations.tsx new file mode 100644 index 000000000..488327f4b --- /dev/null +++ b/src/Augmentation/ui/PurchasableAugmentations.tsx @@ -0,0 +1,264 @@ +/** + * React component for displaying a single augmentation for purchase through + * the faction UI + */ +import { CheckBox, CheckBoxOutlineBlank, CheckCircle, Info, NewReleases, Report } from "@mui/icons-material"; +import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material"; +import React, { useState } from "react"; +import { getNextNeuroFluxLevel } from "../AugmentationHelpers"; +import { Faction } from "../../Faction/Faction"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { Settings } from "../../Settings/Settings"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { Augmentation } from "../Augmentation"; +import { Augmentations } from "../Augmentations"; +import { AugmentationNames } from "../data/AugmentationNames"; +import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal"; + +interface IPreReqsProps { + player: IPlayer; + aug: Augmentation; +} + +const PreReqs = (props: IPreReqsProps): React.ReactElement => { + const ownedPreReqs = props.aug.prereqs.filter((aug) => props.player.hasAugmentation(aug)); + const hasPreReqs = props.aug.prereqs.length > 0 && ownedPreReqs.length === props.aug.prereqs.length; + + return ( + + + This Augmentation has the following pre-requisite(s): + + {props.aug.prereqs.map((preAug) => ( + + ))} + + } + > + + {hasPreReqs ? ( + <> + + Pre-requisites Owned + + ) : ( + <> + + Missing {props.aug.prereqs.length - ownedPreReqs.length} pre-requisite(s) + + )} + + + ); +}; + +interface IExclusiveProps { + player: IPlayer; + aug: Augmentation; +} + +const Exclusive = (props: IExclusiveProps): React.ReactElement => { + return ( + + + This Augmentation can only be acquired from the following source(s): + + + + } + > + + + ); +}; + +interface IReqProps { + value: string; + color: string; + fulfilled: boolean; +} + +const Requirement = (props: IReqProps): React.ReactElement => { + return ( + + {props.fulfilled ? : } + {props.value} + + ); +}; + +interface IPurchasableAugsProps { + augNames: string[]; + ownedAugNames: string[]; + player: IPlayer; + + canPurchase: (player: IPlayer, aug: Augmentation) => boolean; + purchaseAugmentation: (player: IPlayer, aug: Augmentation, showModal: (open: boolean) => void) => void; + + rep?: number; + sleeveAugs?: boolean; + faction?: Faction; +} + +export const PurchasableAugmentations = (props: IPurchasableAugsProps): React.ReactElement => { + return ( + + {props.augNames.map((augName: string) => ( + + ))} + {props.ownedAugNames.map((augName: string) => ( + + ))} + + ); +}; + +interface IPurchasableAugProps { + parent: IPurchasableAugsProps; + augName: string; + owned: boolean; +} + +export function PurchasableAugmentation(props: IPurchasableAugProps): React.ReactElement { + const [open, setOpen] = useState(false); + + const aug = Augmentations[props.augName]; + + const cost = props.parent.sleeveAugs ? aug.startingCost : aug.baseCost; + + const info = typeof aug.info === "string" ? {aug.info} : aug.info; + const description = ( + <> + {info} +
+
+ {aug.stats} + + ); + + return ( + + <> + + + + + + + + {props.augName} + {props.augName === AugmentationNames.NeuroFluxGovernor && ` - Level ${getNextNeuroFluxLevel()}`} + + {description} + + } + > + + + + {aug.name} + {aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${getNextNeuroFluxLevel()}`} + + {aug.factions.length === 1 && !props.parent.sleeveAugs && ( + + )} + + + {aug.prereqs.length > 0 && !props.parent.sleeveAugs && } + + + + {props.owned || ( + + cost} + value={numeralWrapper.formatMoney(cost)} + color={Settings.theme.money} + /> + {props.parent.rep !== undefined && ( + = aug.baseRepRequirement} + value={`${numeralWrapper.formatReputation(aug.baseRepRequirement)} rep`} + color={Settings.theme.rep} + /> + )} + + )} + + {Settings.SuppressBuyAugmentationConfirmation || ( + setOpen(false)} + faction={props.parent.faction} + aug={aug} + /> + )} + + + ); +} diff --git a/src/Faction/ui/PurchaseAugmentationModal.tsx b/src/Augmentation/ui/PurchaseAugmentationModal.tsx similarity index 64% rename from src/Faction/ui/PurchaseAugmentationModal.tsx rename to src/Augmentation/ui/PurchaseAugmentationModal.tsx index 658978aaf..0a6c2e938 100644 --- a/src/Faction/ui/PurchaseAugmentationModal.tsx +++ b/src/Augmentation/ui/PurchaseAugmentationModal.tsx @@ -1,9 +1,9 @@ import React from "react"; -import { Augmentation } from "../../Augmentation/Augmentation"; -import { Faction } from "../Faction"; -import { purchaseAugmentation } from "../FactionHelpers"; -import { isRepeatableAug } from "../../Augmentation/AugmentationHelpers"; +import { Augmentation } from "../Augmentation"; +import { Faction } from "../../Faction/Faction"; +import { purchaseAugmentation } from "../../Faction/FactionHelpers"; +import { isRepeatableAug } from "../AugmentationHelpers"; import { Money } from "../../ui/React/Money"; import { Modal } from "../../ui/React/Modal"; import { use } from "../../ui/Context"; @@ -13,21 +13,23 @@ import Button from "@mui/material/Button"; interface IProps { open: boolean; onClose: () => void; - faction: Faction; - aug: Augmentation; - rerender: () => void; + faction?: Faction; + aug?: Augmentation; } export function PurchaseAugmentationModal(props: IProps): React.ReactElement { + if (typeof props.aug === "undefined" || typeof props.faction === "undefined") { + return <>; + } + const player = use.Player(); function buy(): void { - if (!isRepeatableAug(props.aug) && player.hasAugmentation(props.aug)) { + if (!isRepeatableAug(props.aug as Augmentation) && player.hasAugmentation(props.aug as Augmentation)) { return; } - purchaseAugmentation(props.aug, props.faction); - props.rerender(); + purchaseAugmentation(props.aug as Augmentation, props.faction as Faction); props.onClose(); } diff --git a/src/Constants.ts b/src/Constants.ts index d52f9a0dc..64159ada9 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -120,7 +120,7 @@ export const CONSTANTS: { LatestUpdate: string; } = { VersionString: "1.6.4", - VersionNumber: 15, + VersionNumber: 16, // Speed (in ms) at which the main loop is updated _idleSpeed: 200, diff --git a/src/Electron.tsx b/src/Electron.tsx index ff31fa3c9..2c5cd35fe 100644 --- a/src/Electron.tsx +++ b/src/Electron.tsx @@ -11,11 +11,41 @@ import { exportScripts } from "./Terminal/commands/download"; import { CONSTANTS } from "./Constants"; import { hash } from "./hash/hash"; +interface IReturnWebStatus extends IReturnStatus { + data?: Record; +} + +declare global { + interface Window { + appNotifier: { + terminal: (message: string, type?: string) => void; + toast: (message: string, type: ToastVariant, duration?: number) => void; + }; + appSaveFns: { + triggerSave: () => Promise; + triggerGameExport: () => void; + triggerScriptsExport: () => void; + getSaveData: () => { save: string; fileName: string }; + getSaveInfo: (base64save: string) => Promise; + pushSaveData: (base64save: string, automatic?: boolean) => void; + }; + electronBridge: { + send: (channel: string, data?: unknown) => void; + receive: (channel: string, func: (...args: any[]) => void) => void; + }; + } + interface Document { + getFiles: () => IReturnWebStatus; + deleteFile: (filename: string) => IReturnWebStatus; + saveFile: (filename: string, code: string) => IReturnWebStatus; + } +} + export function initElectron(): void { const userAgent = navigator.userAgent.toLowerCase(); if (userAgent.indexOf(" electron/") > -1) { // Electron-specific code - (document as any).achievements = []; + document.achievements = []; initWebserver(); initAppNotifier(); initSaveFunctions(); @@ -24,11 +54,6 @@ export function initElectron(): void { } function initWebserver(): void { - interface IReturnWebStatus extends IReturnStatus { - data?: { - [propName: string]: any; - }; - } function normalizeFileName(filename: string): string { filename = filename.replace(/\/\/+/g, "/"); filename = removeLeadingSlash(filename); @@ -38,7 +63,7 @@ function initWebserver(): void { return filename; } - (document as any).getFiles = function (): IReturnWebStatus { + document.getFiles = function (): IReturnWebStatus { const home = GetServer("home"); if (home === null) { return { @@ -58,7 +83,7 @@ function initWebserver(): void { }; }; - (document as any).deleteFile = function (filename: string): IReturnWebStatus { + document.deleteFile = function (filename: string): IReturnWebStatus { filename = normalizeFileName(filename); const home = GetServer("home"); if (home === null) { @@ -70,7 +95,7 @@ function initWebserver(): void { return home.removeFile(filename); }; - (document as any).saveFile = function (filename: string, code: string): IReturnWebStatus { + document.saveFile = function (filename: string, code: string): IReturnWebStatus { filename = normalizeFileName(filename); code = Buffer.from(code, "base64").toString(); @@ -115,7 +140,7 @@ function initAppNotifier(): void { }; // Will be consumud by the electron wrapper. - (window as any).appNotifier = funcs; + window.appNotifier = funcs; } function initSaveFunctions(): void { @@ -149,38 +174,38 @@ function initSaveFunctions(): void { }; // Will be consumud by the electron wrapper. - (window as any).appSaveFns = funcs; + window.appSaveFns = funcs; } function initElectronBridge(): void { - const bridge = (window as any).electronBridge as any; + const bridge = window.electronBridge; if (!bridge) return; bridge.receive("get-save-data-request", () => { - const data = (window as any).appSaveFns.getSaveData(); + const data = window.appSaveFns.getSaveData(); bridge.send("get-save-data-response", data); }); bridge.receive("get-save-info-request", async (save: string) => { - const data = await (window as any).appSaveFns.getSaveInfo(save); + const data = await window.appSaveFns.getSaveInfo(save); bridge.send("get-save-info-response", data); }); bridge.receive("push-save-request", ({ save, automatic = false }: { save: string; automatic: boolean }) => { - (window as any).appSaveFns.pushSaveData(save, automatic); + window.appSaveFns.pushSaveData(save, automatic); }); bridge.receive("trigger-save", () => { - return (window as any).appSaveFns + return window.appSaveFns .triggerSave() .then(() => { bridge.send("save-completed"); }) - .catch((error: any) => { + .catch((error: unknown) => { console.log(error); SnackbarEvents.emit("Could not save game.", ToastVariant.ERROR, 2000); }); }); bridge.receive("trigger-game-export", () => { try { - (window as any).appSaveFns.triggerGameExport(); + window.appSaveFns.triggerGameExport(); } catch (error) { console.log(error); SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000); @@ -188,7 +213,7 @@ function initElectronBridge(): void { }); bridge.receive("trigger-scripts-export", () => { try { - (window as any).appSaveFns.triggerScriptsExport(); + window.appSaveFns.triggerScriptsExport(); } catch (error) { console.log(error); SnackbarEvents.emit("Could not export scripts.", ToastVariant.ERROR, 2000); @@ -197,14 +222,14 @@ function initElectronBridge(): void { } export function pushGameSaved(data: SaveData): void { - const bridge = (window as any).electronBridge as any; + const bridge = window.electronBridge; if (!bridge) return; bridge.send("push-game-saved", data); } export function pushGameReady(): void { - const bridge = (window as any).electronBridge as any; + const bridge = window.electronBridge; if (!bridge) return; // Send basic information to the electron wrapper @@ -222,7 +247,7 @@ export function pushGameReady(): void { } export function pushImportResult(wasImported: boolean): void { - const bridge = (window as any).electronBridge as any; + const bridge = window.electronBridge; if (!bridge) return; bridge.send("push-import-result", { wasImported }); @@ -230,7 +255,7 @@ export function pushImportResult(wasImported: boolean): void { } export function pushDisableRestore(): void { - const bridge = (window as any).electronBridge as any; + const bridge = window.electronBridge; if (!bridge) return; bridge.send("push-disable-restore", { duration: 1000 * 60 }); diff --git a/src/Faction/FactionHelpers.tsx b/src/Faction/FactionHelpers.tsx index 84519d5af..73a6e569a 100644 --- a/src/Faction/FactionHelpers.tsx +++ b/src/Faction/FactionHelpers.tsx @@ -54,36 +54,15 @@ export function joinFaction(faction: Faction): void { //Returns a boolean indicating whether the player has the prerequisites for the //specified Augmentation export function hasAugmentationPrereqs(aug: Augmentation): boolean { - let hasPrereqs = true; - if (aug.prereqs && aug.prereqs.length > 0) { - for (let i = 0; i < aug.prereqs.length; ++i) { - const prereqAug = Augmentations[aug.prereqs[i]]; - if (prereqAug == null) { - console.error(`Invalid prereq Augmentation ${aug.prereqs[i]}`); - continue; - } - - if (Player.hasAugmentation(prereqAug, true) === false) { - hasPrereqs = false; - - // Check if the aug is purchased - for (let j = 0; j < Player.queuedAugmentations.length; ++j) { - if (Player.queuedAugmentations[j].name === prereqAug.name) { - hasPrereqs = true; - break; - } - } - } - } - } - - return hasPrereqs; + return aug.prereqs.every((aug) => Player.hasAugmentation(aug)); } export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string { const hasPrereqs = hasAugmentationPrereqs(aug); if (!hasPrereqs) { - const txt = `You must first purchase or install ${aug.prereqs.join(",")} before you can purchase this one.`; + const txt = `You must first purchase or install ${aug.prereqs + .filter((req) => !Player.hasAugmentation(req)) + .join(",")} before you can purchase this one.`; if (sing) { return txt; } else { @@ -165,13 +144,11 @@ export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Factio let augs = Object.values(Augmentations); // Remove special augs - augs = augs.filter((a) => !a.isSpecial); + augs = augs.filter((a) => !a.isSpecial || a.name != AugmentationNames.CongruityImplant); - const blacklist: string[] = [AugmentationNames.NeuroFluxGovernor, AugmentationNames.CongruityImplant]; - - if (player.bitNodeN !== 2) { + if (player.bitNodeN === 2) { // TRP is not available outside of BN2 for Gangs - blacklist.push(AugmentationNames.TheRedPill); + augs.push(Augmentations[AugmentationNames.TheRedPill]); } const rng = SFC32RNG(`BN${player.bitNodeN}.${player.sourceFileLvl(player.bitNodeN)}`); @@ -190,9 +167,6 @@ export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Factio }; augs = augs.filter(uniqueFilter); - // Remove blacklisted augs - augs = augs.filter((a) => !blacklist.includes(a.name)); - return augs.map((a) => a.name); } diff --git a/src/Faction/ui/AugmentationsPage.tsx b/src/Faction/ui/AugmentationsPage.tsx index f9f3664b2..c169520fd 100644 --- a/src/Faction/ui/AugmentationsPage.tsx +++ b/src/Faction/ui/AugmentationsPage.tsx @@ -1,30 +1,21 @@ /** * Root React Component for displaying a faction's "Purchase Augmentations" page */ +import { Box, Button, Tooltip, Typography, Paper, Container } from "@mui/material"; import React, { useState } from "react"; - -import { PurchaseableAugmentation } from "./PurchaseableAugmentation"; - +import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers"; import { Augmentations } from "../../Augmentation/Augmentations"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; -import { Faction } from "../Faction"; +import { PurchasableAugmentations } from "../../Augmentation/ui/PurchasableAugmentations"; import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums"; import { Settings } from "../../Settings/Settings"; -import { hasAugmentationPrereqs, getFactionAugmentationsFiltered } from "../FactionHelpers"; - import { use } from "../../ui/Context"; -import { Reputation } from "../../ui/React/Reputation"; -import { Favor } from "../../ui/React/Favor"; import { numeralWrapper } from "../../ui/numeralFormat"; - -import Box from "@mui/material/Box"; -import Button from "@mui/material/Button"; -import Typography from "@mui/material/Typography"; -import Tooltip from "@mui/material/Tooltip"; -import TableBody from "@mui/material/TableBody"; -import Table from "@mui/material/Table"; -import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers"; +import { Favor } from "../../ui/React/Favor"; +import { Reputation } from "../../ui/React/Reputation"; import { FactionNames } from "../data/FactionNames"; +import { Faction } from "../Faction"; +import { getFactionAugmentationsFiltered, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers"; type IProps = { faction: Faction; @@ -137,38 +128,12 @@ export function AugmentationsPage(props: IProps): React.ReactElement { aug === AugmentationNames.NeuroFluxGovernor || (!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)), ); - - const purchaseableAugmentation = (aug: string, owned = false): React.ReactNode => { - return ( - - ); - }; - - const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug)); - - let ownedElem = <>; const owned = augs.filter((aug: string) => !purchasable.includes(aug)); - if (owned.length !== 0) { - ownedElem = ( - <> -
- Purchased Augmentations - This faction also offers these augmentations but you already own them. - {owned.map((aug) => purchaseableAugmentation(aug, true))} - - ); - } + const multiplierComponent = props.faction.name !== FactionNames.ShadowsOfAnarchy ? ( - Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())} + Price multiplier: x {numeralWrapper.formatReallyBigNumber(getGenericAugmentationPriceMultiplier())} ) : ( <> @@ -176,42 +141,77 @@ export function AugmentationsPage(props: IProps): React.ReactElement { return ( <> - - Faction Augmentations - - These are all of the Augmentations that are available to purchase from {props.faction.name}. Augmentations are - powerful upgrades that will enhance your abilities. -
- Reputation: Favor:{" "} - -
- - + + Faction Augmentations + + + These are all of the Augmentations that are available to purchase from {props.faction.name}. + Augmentations are powerful upgrades that will enhance your abilities. +
+
+ + + The price of every Augmentation increases for every queued Augmentation and it is reset when you + install them. + + } + > + {multiplierComponent} + - The price of every Augmentation increases for every queued Augmentation and it is reset when you install - them. + Reputation: + + Favor: + + + + + + + + +
+ + + { + return ( + hasAugmentationPrereqs(aug) && + props.faction.playerReputation >= aug.baseRepRequirement && + (aug.baseCost === 0 || player.money > aug.baseCost) + ); + }} + purchaseAugmentation={(player, aug, showModal) => { + if (!Settings.SuppressBuyAugmentationConfirmation) { + showModal(true); + } else { + purchaseAugmentation(aug, props.faction); + rerender(); } - > - {multiplierComponent} -
-
- - - - -
- - - {augListElems} -
- - - {ownedElem} -
+ }} + rep={props.faction.playerReputation} + faction={props.faction} + /> ); } diff --git a/src/Faction/ui/PurchaseableAugmentation.tsx b/src/Faction/ui/PurchaseableAugmentation.tsx deleted file mode 100644 index feebb29e0..000000000 --- a/src/Faction/ui/PurchaseableAugmentation.tsx +++ /dev/null @@ -1,170 +0,0 @@ -/** - * React component for displaying a single augmentation for purchase through - * the faction UI - */ -import React, { useState } from "react"; - -import { hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers"; -import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal"; - -import { Augmentations } from "../../Augmentation/Augmentations"; -import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; -import { Faction } from "../Faction"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { Settings } from "../../Settings/Settings"; -import { Money } from "../../ui/React/Money"; -import { Reputation } from "../../ui/React/Reputation"; - -import { Augmentation as AugFormat } from "../../ui/React/Augmentation"; -import Button from "@mui/material/Button"; -import Typography from "@mui/material/Typography"; -import Tooltip from "@mui/material/Tooltip"; -import Box from "@mui/material/Box"; -import { TableCell } from "../../ui/React/Table"; -import TableRow from "@mui/material/TableRow"; -import { getNextNeuroFluxLevel } from "../../Augmentation/AugmentationHelpers"; - -interface IReqProps { - augName: string; - p: IPlayer; - hasReq: boolean; - rep: number; - hasRep: boolean; - cost: number; - hasCost: boolean; -} - -function Requirements(props: IReqProps): React.ReactElement { - const aug = Augmentations[props.augName]; - if (!props.hasReq) { - return ( - - - Requires{" "} - {aug.prereqs.map((aug, i) => ( - - ))} - - - ); - } - - return ( - - - - - - - - - Requires faction reputation - - - - ); -} - -interface IProps { - augName: string; - faction: Faction; - p: IPlayer; - rerender: () => void; - owned?: boolean; -} - -export function PurchaseableAugmentation(props: IProps): React.ReactElement { - const [open, setOpen] = useState(false); - const aug = Augmentations[props.augName]; - if (aug == null) throw new Error(`aug ${props.augName} does not exists`); - - if (aug == null) { - console.error( - `Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${props.augName}`, - ); - return <>; - } - - const moneyCost = aug.baseCost; - const repCost = aug.baseRepRequirement; - const hasReq = hasAugmentationPrereqs(aug); - const hasRep = props.faction.playerReputation >= repCost; - const hasCost = aug.baseCost === 0 || props.p.money > aug.baseCost; - - // Determine UI properties - const color: "error" | "primary" = !hasReq || !hasRep || !hasCost ? "error" : "primary"; - - // Determine button txt - let btnTxt = aug.name; - if (aug.name === AugmentationNames.NeuroFluxGovernor) { - btnTxt += ` - Level ${getNextNeuroFluxLevel()}`; - } - - let tooltip = <>; - if (typeof aug.info === "string") { - tooltip = ( - <> - {aug.info} -
-
- {aug.stats} - - ); - } else - tooltip = ( - <> - {aug.info} -
-
- {aug.stats} - - ); - - function handleClick(): void { - if (color === "error") return; - if (!Settings.SuppressBuyAugmentationConfirmation) { - setOpen(true); - } else { - purchaseAugmentation(aug, props.faction); - props.rerender(); - } - } - - return ( - - {!props.owned && ( - - - setOpen(false)} - aug={aug} - faction={props.faction} - rerender={props.rerender} - /> - - )} - - - {tooltip}} placement="top"> - {btnTxt} - - - - {!props.owned && ( - - )} - - ); -} diff --git a/src/Infiltration/formulas/victory.ts b/src/Infiltration/formulas/victory.ts index 184cf15f9..2c781bef8 100644 --- a/src/Infiltration/formulas/victory.ts +++ b/src/Infiltration/formulas/victory.ts @@ -31,9 +31,9 @@ export function calculateTradeInformationRepReward( const levelBonus = maxLevel * Math.pow(1.01, maxLevel); return ( - Math.pow(reward + 1, 2) * - Math.pow(difficulty, 3) * - 3e3 * + Math.pow(reward + 1, 1.1) * + Math.pow(difficulty, 1.2) * + 30 * levelBonus * (player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.5 : 1) * BitNodeMultipliers.InfiltrationMoney diff --git a/src/PersonObjects/Grafting/GraftingHelpers.ts b/src/PersonObjects/Grafting/GraftingHelpers.ts index edf5ca798..62d3b72f4 100644 --- a/src/PersonObjects/Grafting/GraftingHelpers.ts +++ b/src/PersonObjects/Grafting/GraftingHelpers.ts @@ -1,5 +1,4 @@ import { Augmentations } from "../../Augmentation/Augmentations"; -import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { GraftableAugmentation } from "./GraftableAugmentation"; import { IPlayer } from "../IPlayer"; @@ -7,8 +6,7 @@ export const getGraftingAvailableAugs = (player: IPlayer): string[] => { const augs: string[] = []; for (const [augName, aug] of Object.entries(Augmentations)) { - if (augName === AugmentationNames.NeuroFluxGovernor || augName === AugmentationNames.TheRedPill || aug.isSpecial) - continue; + if (aug.isSpecial) continue; augs.push(augName); } diff --git a/src/PersonObjects/Grafting/ui/GraftingRoot.tsx b/src/PersonObjects/Grafting/ui/GraftingRoot.tsx index adc741589..3e7ddfd28 100644 --- a/src/PersonObjects/Grafting/ui/GraftingRoot.tsx +++ b/src/PersonObjects/Grafting/ui/GraftingRoot.tsx @@ -139,34 +139,37 @@ export const GraftingRoot = (): React.ReactElement => { } /> - - Time to Graft:{" "} - {convertTimeMsToTimeElapsedString( - calculateGraftingTimeWithBonus(player, GraftableAugmentations[selectedAug]), + + + Time to Graft:{" "} + {convertTimeMsToTimeElapsedString( + calculateGraftingTimeWithBonus(player, GraftableAugmentations[selectedAug]), + )} + {/* Use formula so the displayed creation time is accurate to player bonus */} + + + {Augmentations[selectedAug].prereqs.length > 0 && ( + )} - {/* Use formula so the displayed creation time is accurate to player bonus */} - - {Augmentations[selectedAug].prereqs.length > 0 && ( - - )} +
-
- - {(() => { - const aug = Augmentations[selectedAug]; + + {(() => { + const aug = Augmentations[selectedAug]; - const info = typeof aug.info === "string" ? {aug.info} : aug.info; - const tooltip = ( - <> - {info} -
-
- {aug.stats} - - ); - return tooltip; - })()} -
+ const info = typeof aug.info === "string" ? {aug.info} : aug.info; + const tooltip = ( + <> + {info} +
+
+ {aug.stats} + + ); + return tooltip; + })()} +
+ ) : ( diff --git a/src/PersonObjects/Sleeve/SleeveHelpers.ts b/src/PersonObjects/Sleeve/SleeveHelpers.ts index 76bde04e8..324cd9aa6 100644 --- a/src/PersonObjects/Sleeve/SleeveHelpers.ts +++ b/src/PersonObjects/Sleeve/SleeveHelpers.ts @@ -5,7 +5,6 @@ import { IPlayer } from "../IPlayer"; import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentations } from "../../Augmentation/Augmentations"; -import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { Faction } from "../../Faction/Faction"; import { Factions } from "../../Faction/Factions"; @@ -22,9 +21,6 @@ export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentat // Helper function that helps filter out augs that are already owned // and augs that aren't allowed for sleeves function isAvailableForSleeve(aug: Augmentation): boolean { - if (aug.name === AugmentationNames.NeuroFluxGovernor) { - return false; - } if (ownedAugNames.includes(aug.name)) { return false; } diff --git a/src/PersonObjects/Sleeve/ui/SleeveAugmentationsModal.tsx b/src/PersonObjects/Sleeve/ui/SleeveAugmentationsModal.tsx index e17118e8c..71eaf08a4 100644 --- a/src/PersonObjects/Sleeve/ui/SleeveAugmentationsModal.tsx +++ b/src/PersonObjects/Sleeve/ui/SleeveAugmentationsModal.tsx @@ -1,20 +1,10 @@ -import React, { useState, useEffect } from "react"; +import { Container, Typography, Paper } from "@mui/material"; +import React, { useEffect, useState } from "react"; +import { PurchasableAugmentations } from "../../../Augmentation/ui/PurchasableAugmentations"; +import { use } from "../../../ui/Context"; +import { Modal } from "../../../ui/React/Modal"; import { Sleeve } from "../Sleeve"; import { findSleevePurchasableAugs } from "../SleeveHelpers"; -import { Augmentations } from "../../../Augmentation/Augmentations"; -import { Augmentation } from "../../../Augmentation/Augmentation"; -import { Money } from "../../../ui/React/Money"; -import { Modal } from "../../../ui/React/Modal"; -import { use } from "../../../ui/Context"; -import Typography from "@mui/material/Typography"; -import Tooltip from "@mui/material/Tooltip"; -import Paper from "@mui/material/Paper"; -import Box from "@mui/material/Box"; -import Button from "@mui/material/Button"; -import TableBody from "@mui/material/TableBody"; -import Table from "@mui/material/Table"; -import { TableCell } from "../../../ui/React/Table"; -import TableRow from "@mui/material/TableRow"; interface IProps { open: boolean; @@ -42,80 +32,34 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement { // and you must also have enough rep in that faction in order to purchase it. const availableAugs = findSleevePurchasableAugs(props.sleeve, player); - function purchaseAugmentation(aug: Augmentation): void { - props.sleeve.tryBuyAugmentation(player, aug); - rerender(); - } - return ( - <> - - - You can purchase Augmentations for your Duplicate Sleeves. These Augmentations have the same effect as they - would for you. You can only purchase Augmentations that you have unlocked through Factions. -
-
- When purchasing an Augmentation for a Duplicate Sleeve, they are immediately installed. This means that the - Duplicate Sleeve will immediately lose all of its stat experience. -
- - - - {availableAugs.map((aug) => { - return ( - - - - - - - - {aug.name} - - - - - - - - ); - })} - -
-
-
- - {ownedAugNames.length > 0 && ( - <> - Owned Augmentations: - - {ownedAugNames.map((augName) => { - const aug = Augmentations[augName]; - const info = typeof aug.info === "string" ? {aug.info} : aug.info; - const tooltip = ( - <> - {info} -
-
- {aug.stats} - - ); - - return ( - {tooltip}}> - - {augName} - - - ); - })} -
- - )} - + + + You can purchase Augmentations for your Duplicate Sleeves. These Augmentations have the same effect as they + would for you. You can only purchase Augmentations that you have unlocked through Factions. +
+
+ When purchasing an Augmentation for a Duplicate Sleeve, they are immediately installed. This means that the + Duplicate Sleeve will immediately lose all of its stat experience. +
+
+ Augmentations will appear below as they become available. +
+
+ aug.name)} + ownedAugNames={ownedAugNames} + player={player} + canPurchase={(player, aug) => { + return player.money > aug.startingCost; + }} + purchaseAugmentation={(player, aug, _showModal) => { + props.sleeve.tryBuyAugmentation(player, aug); + rerender(); + }} + sleeveAugs + />
); } diff --git a/src/SaveObject.tsx b/src/SaveObject.tsx index dafaca6aa..5367ad25c 100755 --- a/src/SaveObject.tsx +++ b/src/SaveObject.tsx @@ -400,8 +400,19 @@ function evaluateVersionCompatibility(ver: string | number): void { if (ver < 15) { (Settings as any).EditorTheme = { ...defaultMonacoTheme }; } + //Fix contract names if (ver < 16) { Factions[FactionNames.ShadowsOfAnarchy] = new Faction(FactionNames.ShadowsOfAnarchy); + //Iterate over all contracts on all servers + for (const server of GetAllServers()) { + for (const contract of server.contracts) { + //Rename old "HammingCodes: Integer to encoded Binary" contracts + //to "HammingCodes: Integer to Encoded Binary" + if (contract.type == "HammingCodes: Integer to encoded Binary") { + contract.type = "HammingCodes: Integer to Encoded Binary"; + } + } + } } } } diff --git a/src/data/codingcontracttypes.ts b/src/data/codingcontracttypes.ts index f9c11f4a2..a28f8d2d3 100644 --- a/src/data/codingcontracttypes.ts +++ b/src/data/codingcontracttypes.ts @@ -1250,7 +1250,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ }, }, { - name: "HammingCodes: Integer to encoded Binary", + name: "HammingCodes: Integer to Encoded Binary", numTries: 10, difficulty: 5, desc: (n: number): string => {