merge base

This commit is contained in:
phyzical 2022-04-27 19:00:42 +08:00
commit af8284381c
18 changed files with 528 additions and 291 deletions

@ -1,8 +1,13 @@
// Defined by webpack on startup or compilation // 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 // When using file-loader, we'll get a path to the resource
declare module "*.png" { declare module "*.png" {
const value: string; const value: string;
export default value; export default value;
} }
// Achievements communicated back to Electron shell for Steam.
declare interface Document {
achievements: string[];
}

@ -799,5 +799,5 @@ export function calculateAchievements(): void {
// Write all player's achievements to document for Steam/Electron // Write all player's achievements to document for Steam/Electron
// This could be replaced by "availableAchievements" // This could be replaced by "availableAchievements"
// if we don't want to grant the save game achievements to steam but only currently available // 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)];
} }

@ -109,6 +109,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
rewards, reduced damage taken, etc. rewards, reduced damage taken, etc.
</> </>
), ),
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
@ -121,6 +122,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
stats: ( stats: (
<>This augmentation makes the Slash minigame easier by showing you via an indictor when the slash in coming.</> <>This augmentation makes the Slash minigame easier by showing you via an indictor when the slash in coming.</>
), ),
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
@ -129,6 +131,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
moneyCost: 1e6, moneyCost: 1e6,
info: "A connective brain implant to SASHA that focuses in pattern recognition and predictive templating.", 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 '[' ']'.</>, stats: <>This augmentation makes the Bracket minigame easier by removing all '[' ']'.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
@ -137,6 +140,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
moneyCost: 1e6, moneyCost: 1e6,
info: "Opto-occipito implant to process visual signal before brain interpretation.", info: "Opto-occipito implant to process visual signal before brain interpretation.",
stats: <>This augmentation makes the Backwards minigame easier by flipping the words.</>, stats: <>This augmentation makes the Backwards minigame easier by flipping the words.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
@ -147,6 +151,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
"Pheromone extruder injected in the thoracodorsal nerve. Emits pleasing scent guaranteed to " + "Pheromone extruder injected in the thoracodorsal nerve. Emits pleasing scent guaranteed to " +
"make conversational partners more agreeable.", "make conversational partners more agreeable.",
stats: <>This augmentation makes the Bribe minigame easier by indicating the incorrect paths.</>, stats: <>This augmentation makes the Bribe minigame easier by indicating the incorrect paths.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
@ -155,6 +160,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
moneyCost: 1e6, moneyCost: 1e6,
info: "Penta-dynamo-neurovascular-valve inserted in the carpal ligament, enhances dexterity.", 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.</>, stats: <>This augmentation makes the Cheat Code minigame easier by allowing the opposite character.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
@ -163,6 +169,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
moneyCost: 1e6, moneyCost: 1e6,
info: "Transtinatium VVD reticulator used in optico-sterbing recognition.", info: "Transtinatium VVD reticulator used in optico-sterbing recognition.",
stats: <>This augmentation makes the Symbol matching minigame easier by indicating the correct choice.</>, stats: <>This augmentation makes the Symbol matching minigame easier by indicating the correct choice.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
@ -176,6 +183,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
position. position.
</> </>
), ),
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
new Augmentation({ new Augmentation({
@ -184,6 +192,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
moneyCost: 1e6, moneyCost: 1e6,
info: "Neodynic retention fjengeln spoofer using -φ karmions, net positive effect on implantees delta wave.", 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.</>, stats: <>This augmentation makes the Wire Cutting minigame easier by indicating the incorrect wires.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy], factions: [FactionNames.ShadowsOfAnarchy],
}), }),
]; ];
@ -242,7 +251,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
moneyCost: 1.15e8, moneyCost: 1.15e8,
repCost: 2.75e4, repCost: 2.75e4,
info: "The latest version of the 'Augmented Targeting' implant adds the ability to lock-on and track threats.", 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, dexterity_mult: 1.3,
factions: [ factions: [
FactionNames.TheDarkArmy, FactionNames.TheDarkArmy,
@ -339,7 +348,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
info: info:
"The latest version of the 'Combat Rib' augmentation releases advanced anabolic steroids that " + "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.", "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, strength_mult: 1.18,
defense_mult: 1.18, defense_mult: 1.18,
factions: [ factions: [
@ -673,7 +682,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"This upgraded firmware allows the Embedded Netburner Module to control information on " + "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 " + "a network by re-routing traffic, spoofing IP addresses, and altering the data inside network " +
"packets.", "packets.",
prereqs: [AugmentationNames.ENMCore], prereqs: [AugmentationNames.ENMCore, AugmentationNames.ENM],
hacking_speed_mult: 1.05, hacking_speed_mult: 1.05,
hacking_money_mult: 1.3, hacking_money_mult: 1.3,
hacking_chance_mult: 1.05, 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. " + "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 " + "This upgraded firmware allows the Embedded Netburner Module to seamlessly inject code into " +
"any device on a network.", "any device on a network.",
prereqs: [AugmentationNames.ENMCoreV2], prereqs: [AugmentationNames.ENMCoreV2, AugmentationNames.ENMCore, AugmentationNames.ENM],
hacking_speed_mult: 1.05, hacking_speed_mult: 1.05,
hacking_money_mult: 1.4, hacking_money_mult: 1.4,
hacking_chance_mult: 1.1, hacking_chance_mult: 1.1,
@ -826,7 +835,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " + "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 " + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.", "so that the brain doesn't have to.",
prereqs: [AugmentationNames.CranialSignalProcessorsG2], prereqs: [AugmentationNames.CranialSignalProcessorsG2, AugmentationNames.CranialSignalProcessorsG1],
hacking_speed_mult: 1.02, hacking_speed_mult: 1.02,
hacking_money_mult: 1.15, hacking_money_mult: 1.15,
hacking_mult: 1.09, hacking_mult: 1.09,
@ -841,7 +850,11 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " + "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 " + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.", "so that the brain doesn't have to.",
prereqs: [AugmentationNames.CranialSignalProcessorsG3], prereqs: [
AugmentationNames.CranialSignalProcessorsG3,
AugmentationNames.CranialSignalProcessorsG2,
AugmentationNames.CranialSignalProcessorsG1,
],
hacking_speed_mult: 1.02, hacking_speed_mult: 1.02,
hacking_money_mult: 1.2, hacking_money_mult: 1.2,
hacking_grow_mult: 1.25, hacking_grow_mult: 1.25,
@ -856,7 +869,12 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " + "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 " + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.", "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_mult: 1.3,
hacking_money_mult: 1.25, hacking_money_mult: 1.25,
hacking_grow_mult: 1.75, hacking_grow_mult: 1.75,
@ -1254,6 +1272,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
moneyCost: 0, moneyCost: 0,
info: "It's time to leave the cave.", info: "It's time to leave the cave.",
stats: null, stats: null,
isSpecial: true,
factions: [FactionNames.Daedalus], factions: [FactionNames.Daedalus],
}), }),
new Augmentation({ 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 " + "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 " + "fully and wholly free of it's accursed toll. Serenity brings tranquility the form " +
"of no longer suffering a stat penalty. ", "of no longer suffering a stat penalty. ",
prereqs: [AugmentationNames.StaneksGift2], prereqs: [AugmentationNames.StaneksGift2, AugmentationNames.StaneksGift1],
isSpecial: true, isSpecial: true,
hacking_chance_mult: 1 / 0.95, hacking_chance_mult: 1 / 0.95,
hacking_speed_mult: 1 / 0.95, hacking_speed_mult: 1 / 0.95,
@ -2003,6 +2022,7 @@ export function initNeuroFluxGovernor(): Augmentation {
multiplicatively. multiplicatively.
</> </>
), ),
isSpecial: true,
hacking_chance_mult: 1.01 + donationBonus, hacking_chance_mult: 1.01 + donationBonus,
hacking_speed_mult: 1.01 + donationBonus, hacking_speed_mult: 1.01 + donationBonus,
hacking_money_mult: 1.01 + donationBonus, hacking_money_mult: 1.01 + donationBonus,

@ -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 { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentation } from "../Augmentation";
import { AugmentationNames } from "../data/AugmentationNames";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
import { StaticAugmentations } from "../StaticAugmentations";
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 (
<Tooltip
title={
<>
<Typography sx={{ color: Settings.theme.money }}>
This Augmentation has the following pre-requisite(s):
</Typography>
{props.aug.prereqs.map((preAug) => (
<Requirement
fulfilled={props.player.hasAugmentation(preAug)}
value={preAug}
color={Settings.theme.money}
key={preAug}
/>
))}
</>
}
>
<Typography
variant="body2"
sx={{
display: "flex",
alignItems: "center",
color: hasPreReqs ? Settings.theme.successlight : Settings.theme.error,
}}
>
{hasPreReqs ? (
<>
<CheckCircle fontSize="small" sx={{ mr: 1 }} />
Pre-requisites Owned
</>
) : (
<>
<Report fontSize="small" sx={{ mr: 1 }} />
Missing {props.aug.prereqs.length - ownedPreReqs.length} pre-requisite(s)
</>
)}
</Typography>
</Tooltip>
);
};
interface IExclusiveProps {
player: IPlayer;
aug: Augmentation;
}
const Exclusive = (props: IExclusiveProps): React.ReactElement => {
return (
<Tooltip
title={
<>
<Typography sx={{ color: Settings.theme.money }}>
This Augmentation can only be acquired from the following source(s):
</Typography>
<ul>
<Typography sx={{ color: Settings.theme.money }}>
<li>
<b>{props.aug.factions[0]}</b> faction
</li>
{props.player.canAccessGang() && !props.aug.isSpecial && (
<li>
Certain <b>gangs</b>
</li>
)}
{props.player.canAccessGrafting() &&
!props.aug.isSpecial &&
props.aug.name !== AugmentationNames.TheRedPill && (
<li>
<b>Grafting</b>
</li>
)}
</Typography>
</ul>
</>
}
>
<NewReleases sx={{ ml: 1, color: Settings.theme.money, transform: "rotate(180deg)" }} />
</Tooltip>
);
};
interface IReqProps {
value: string;
color: string;
fulfilled: boolean;
}
const Requirement = (props: IReqProps): React.ReactElement => {
return (
<Typography sx={{ display: "flex", alignItems: "center", color: props.color }}>
{props.fulfilled ? <CheckBox sx={{ mr: 1 }} /> : <CheckBoxOutlineBlank sx={{ mr: 1 }} />}
{props.value}
</Typography>
);
};
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 (
<Container
maxWidth="lg"
disableGutters
sx={{ mx: 0, display: "grid", gridTemplateColumns: "repeat(1, 1fr)", gap: 1 }}
>
{props.augNames.map((augName: string) => (
<PurchasableAugmentation key={augName} parent={props} augName={augName} owned={false} />
))}
{props.ownedAugNames.map((augName: string) => (
<PurchasableAugmentation key={augName} parent={props} augName={augName} owned={true} />
))}
</Container>
);
};
interface IPurchasableAugProps {
parent: IPurchasableAugsProps;
augName: string;
owned: boolean;
}
export function PurchasableAugmentation(props: IPurchasableAugProps): React.ReactElement {
const [open, setOpen] = useState(false);
const aug = StaticAugmentations[props.augName];
const augCosts = aug.getCost(props.parent.player);
const cost = props.parent.sleeveAugs ? aug.baseCost : augCosts.moneyCost;
const repCost = augCosts.repCost;
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const description = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return (
<Paper
sx={{
p: 1,
display: "grid",
gridTemplateColumns: "minmax(0, 4fr) 1fr",
gap: 1,
opacity: props.owned ? 0.75 : 1,
}}
>
<>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Button
onClick={() =>
props.parent.purchaseAugmentation(props.parent.player, aug, (open): void => {
setOpen(open);
})
}
disabled={!props.parent.canPurchase(props.parent.player, aug) || props.owned}
sx={{ width: "48px", height: "48px", float: "left", clear: "none", mr: 1 }}
>
{props.owned ? "Owned" : "Buy"}
</Button>
<Box sx={{ maxWidth: props.owned ? "100%" : "85%" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Tooltip
title={
<>
<Typography variant="h5">
{props.augName}
{props.augName === AugmentationNames.NeuroFluxGovernor &&
` - Level ${aug.getLevel(props.parent.player)}`}
</Typography>
<Typography>{description}</Typography>
</>
}
>
<Info sx={{ mr: 1 }} color="info" />
</Tooltip>
<Typography
variant="h6"
sx={{
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
}}
>
{aug.name}
{aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${aug.getLevel(props.parent.player)}`}
</Typography>
{aug.factions.length === 1 && !props.parent.sleeveAugs && (
<Exclusive player={props.parent.player} aug={aug} />
)}
</Box>
{aug.prereqs.length > 0 && !props.parent.sleeveAugs && <PreReqs player={props.parent.player} aug={aug} />}
</Box>
</Box>
{props.owned || (
<Box sx={{ display: "grid", alignItems: "center", justifyItems: "left" }}>
<Requirement
fulfilled={cost === 0 || props.parent.player.money > cost}
value={numeralWrapper.formatMoney(cost)}
color={Settings.theme.money}
/>
{props.parent.rep !== undefined && (
<Requirement
fulfilled={props.parent.rep >= repCost}
value={`${numeralWrapper.formatReputation(repCost)} rep`}
color={Settings.theme.rep}
/>
)}
</Box>
)}
{Settings.SuppressBuyAugmentationConfirmation || (
<PurchaseAugmentationModal
open={open}
onClose={() => setOpen(false)}
faction={props.parent.faction}
aug={aug}
/>
)}
</>
</Paper>
);
}

@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentation } from "../Augmentation";
import { Faction } from "../Faction"; import { Faction } from "../../Faction/Faction";
import { purchaseAugmentation } from "../FactionHelpers"; import { purchaseAugmentation } from "../../Faction/FactionHelpers";
import { isRepeatableAug } from "../../Augmentation/AugmentationHelpers"; import { isRepeatableAug } from "../AugmentationHelpers";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../ui/React/Modal";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
@ -13,21 +13,23 @@ import Button from "@mui/material/Button";
interface IProps { interface IProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
faction: Faction; faction?: Faction;
aug: Augmentation; aug?: Augmentation;
rerender: () => void;
} }
export function PurchaseAugmentationModal(props: IProps): React.ReactElement { export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
if (typeof props.aug === "undefined" || typeof props.faction === "undefined") {
return <></>;
}
const player = use.Player(); const player = use.Player();
function buy(): void { function buy(): void {
if (!isRepeatableAug(props.aug) && player.hasAugmentation(props.aug)) { if (!isRepeatableAug(props.aug as Augmentation) && player.hasAugmentation(props.aug as Augmentation)) {
return; return;
} }
purchaseAugmentation(props.aug, props.faction); purchaseAugmentation(props.aug as Augmentation, props.faction as Faction);
props.rerender();
props.onClose(); props.onClose();
} }

@ -120,7 +120,7 @@ export const CONSTANTS: {
LatestUpdate: string; LatestUpdate: string;
} = { } = {
VersionString: "1.6.4", VersionString: "1.6.4",
VersionNumber: 15, VersionNumber: 16,
// Speed (in ms) at which the main loop is updated // Speed (in ms) at which the main loop is updated
_idleSpeed: 200, _idleSpeed: 200,

@ -11,11 +11,41 @@ import { exportScripts } from "./Terminal/commands/download";
import { CONSTANTS } from "./Constants"; import { CONSTANTS } from "./Constants";
import { hash } from "./hash/hash"; import { hash } from "./hash/hash";
interface IReturnWebStatus extends IReturnStatus {
data?: Record<string, unknown>;
}
declare global {
interface Window {
appNotifier: {
terminal: (message: string, type?: string) => void;
toast: (message: string, type: ToastVariant, duration?: number) => void;
};
appSaveFns: {
triggerSave: () => Promise<void>;
triggerGameExport: () => void;
triggerScriptsExport: () => void;
getSaveData: () => { save: string; fileName: string };
getSaveInfo: (base64save: string) => Promise<ImportPlayerData | undefined>;
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 { export function initElectron(): void {
const userAgent = navigator.userAgent.toLowerCase(); const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf(" electron/") > -1) { if (userAgent.indexOf(" electron/") > -1) {
// Electron-specific code // Electron-specific code
(document as any).achievements = []; document.achievements = [];
initWebserver(); initWebserver();
initAppNotifier(); initAppNotifier();
initSaveFunctions(); initSaveFunctions();
@ -24,11 +54,6 @@ export function initElectron(): void {
} }
function initWebserver(): void { function initWebserver(): void {
interface IReturnWebStatus extends IReturnStatus {
data?: {
[propName: string]: any;
};
}
function normalizeFileName(filename: string): string { function normalizeFileName(filename: string): string {
filename = filename.replace(/\/\/+/g, "/"); filename = filename.replace(/\/\/+/g, "/");
filename = removeLeadingSlash(filename); filename = removeLeadingSlash(filename);
@ -38,7 +63,7 @@ function initWebserver(): void {
return filename; return filename;
} }
(document as any).getFiles = function (): IReturnWebStatus { document.getFiles = function (): IReturnWebStatus {
const home = GetServer("home"); const home = GetServer("home");
if (home === null) { if (home === null) {
return { 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); filename = normalizeFileName(filename);
const home = GetServer("home"); const home = GetServer("home");
if (home === null) { if (home === null) {
@ -70,7 +95,7 @@ function initWebserver(): void {
return home.removeFile(filename); 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); filename = normalizeFileName(filename);
code = Buffer.from(code, "base64").toString(); code = Buffer.from(code, "base64").toString();
@ -115,7 +140,7 @@ function initAppNotifier(): void {
}; };
// Will be consumud by the electron wrapper. // Will be consumud by the electron wrapper.
(window as any).appNotifier = funcs; window.appNotifier = funcs;
} }
function initSaveFunctions(): void { function initSaveFunctions(): void {
@ -149,38 +174,38 @@ function initSaveFunctions(): void {
}; };
// Will be consumud by the electron wrapper. // Will be consumud by the electron wrapper.
(window as any).appSaveFns = funcs; window.appSaveFns = funcs;
} }
function initElectronBridge(): void { function initElectronBridge(): void {
const bridge = (window as any).electronBridge as any; const bridge = window.electronBridge;
if (!bridge) return; if (!bridge) return;
bridge.receive("get-save-data-request", () => { 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.send("get-save-data-response", data);
}); });
bridge.receive("get-save-info-request", async (save: string) => { 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.send("get-save-info-response", data);
}); });
bridge.receive("push-save-request", ({ save, automatic = false }: { save: string; automatic: boolean }) => { 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", () => { bridge.receive("trigger-save", () => {
return (window as any).appSaveFns return window.appSaveFns
.triggerSave() .triggerSave()
.then(() => { .then(() => {
bridge.send("save-completed"); bridge.send("save-completed");
}) })
.catch((error: any) => { .catch((error: unknown) => {
console.log(error); console.log(error);
SnackbarEvents.emit("Could not save game.", ToastVariant.ERROR, 2000); SnackbarEvents.emit("Could not save game.", ToastVariant.ERROR, 2000);
}); });
}); });
bridge.receive("trigger-game-export", () => { bridge.receive("trigger-game-export", () => {
try { try {
(window as any).appSaveFns.triggerGameExport(); window.appSaveFns.triggerGameExport();
} catch (error) { } catch (error) {
console.log(error); console.log(error);
SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000); SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000);
@ -188,7 +213,7 @@ function initElectronBridge(): void {
}); });
bridge.receive("trigger-scripts-export", () => { bridge.receive("trigger-scripts-export", () => {
try { try {
(window as any).appSaveFns.triggerScriptsExport(); window.appSaveFns.triggerScriptsExport();
} catch (error) { } catch (error) {
console.log(error); console.log(error);
SnackbarEvents.emit("Could not export scripts.", ToastVariant.ERROR, 2000); SnackbarEvents.emit("Could not export scripts.", ToastVariant.ERROR, 2000);
@ -197,14 +222,14 @@ function initElectronBridge(): void {
} }
export function pushGameSaved(data: SaveData): void { export function pushGameSaved(data: SaveData): void {
const bridge = (window as any).electronBridge as any; const bridge = window.electronBridge;
if (!bridge) return; if (!bridge) return;
bridge.send("push-game-saved", data); bridge.send("push-game-saved", data);
} }
export function pushGameReady(): void { export function pushGameReady(): void {
const bridge = (window as any).electronBridge as any; const bridge = window.electronBridge;
if (!bridge) return; if (!bridge) return;
// Send basic information to the electron wrapper // Send basic information to the electron wrapper
@ -222,7 +247,7 @@ export function pushGameReady(): void {
} }
export function pushImportResult(wasImported: boolean): void { export function pushImportResult(wasImported: boolean): void {
const bridge = (window as any).electronBridge as any; const bridge = window.electronBridge;
if (!bridge) return; if (!bridge) return;
bridge.send("push-import-result", { wasImported }); bridge.send("push-import-result", { wasImported });
@ -230,7 +255,7 @@ export function pushImportResult(wasImported: boolean): void {
} }
export function pushDisableRestore(): void { export function pushDisableRestore(): void {
const bridge = (window as any).electronBridge as any; const bridge = window.electronBridge;
if (!bridge) return; if (!bridge) return;
bridge.send("push-disable-restore", { duration: 1000 * 60 }); bridge.send("push-disable-restore", { duration: 1000 * 60 });

@ -53,37 +53,16 @@ export function joinFaction(faction: Faction): void {
//Returns a boolean indicating whether the player has the prerequisites for the //Returns a boolean indicating whether the player has the prerequisites for the
//specified Augmentation //specified Augmentation
export function hasAugmentationPrereqs(aug: Augmentation): boolean { export function hasAugmentationPrereqs(aug: Augmentation): boolean {
let hasPrereqs = true; return aug.prereqs.every((aug) => Player.hasAugmentation(aug));
if (aug.prereqs && aug.prereqs.length > 0) {
for (let i = 0; i < aug.prereqs.length; ++i) {
const prereqAug = StaticAugmentations[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;
} }
export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string { export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string {
const hasPrereqs = hasAugmentationPrereqs(aug); const hasPrereqs = hasAugmentationPrereqs(aug);
const augCosts = aug.getCost(Player); const augCosts = aug.getCost(Player);
if (!hasPrereqs) { 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) { if (sing) {
return txt; return txt;
} else { } else {
@ -163,13 +142,11 @@ export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Factio
let augs = Object.values(StaticAugmentations); let augs = Object.values(StaticAugmentations);
// Remove special augs // 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 // TRP is not available outside of BN2 for Gangs
blacklist.push(AugmentationNames.TheRedPill); augs.push(StaticAugmentations[AugmentationNames.TheRedPill]);
} }
const rng = SFC32RNG(`BN${player.bitNodeN}.${player.sourceFileLvl(player.bitNodeN)}`); const rng = SFC32RNG(`BN${player.bitNodeN}.${player.sourceFileLvl(player.bitNodeN)}`);
@ -188,9 +165,6 @@ export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Factio
}; };
augs = augs.filter(uniqueFilter); augs = augs.filter(uniqueFilter);
// Remove blacklisted augs
augs = augs.filter((a) => !blacklist.includes(a.name));
return augs.map((a) => a.name); return augs.map((a) => a.name);
} }

@ -1,30 +1,22 @@
/** /**
* Root React Component for displaying a faction's "Purchase Augmentations" page * 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 React, { useState } from "react";
import { PurchaseableAugmentation } from "./PurchaseableAugmentation";
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations"; import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../Faction"; import { PurchasableAugmentations } from "../../Augmentation/ui/PurchasableAugmentations";
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums"; import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { hasAugmentationPrereqs, getFactionAugmentationsFiltered } from "../FactionHelpers";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Favor } from "../../ui/React/Favor";
import Box from "@mui/material/Box"; import { Reputation } from "../../ui/React/Reputation";
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 { FactionNames } from "../data/FactionNames"; import { FactionNames } from "../data/FactionNames";
import { Faction } from "../Faction";
import { getFactionAugmentationsFiltered, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
type IProps = { type IProps = {
faction: Faction; faction: Faction;
@ -138,38 +130,12 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
aug === AugmentationNames.NeuroFluxGovernor || aug === AugmentationNames.NeuroFluxGovernor ||
(!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)), (!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)),
); );
const purchaseableAugmentation = (aug: string, owned = false): React.ReactNode => {
return (
<PurchaseableAugmentation
augName={aug}
faction={props.faction}
key={aug}
p={player}
rerender={rerender}
owned={owned}
/>
);
};
const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug));
let ownedElem = <></>;
const owned = augs.filter((aug: string) => !purchasable.includes(aug)); const owned = augs.filter((aug: string) => !purchasable.includes(aug));
if (owned.length !== 0) {
ownedElem = (
<>
<br />
<Typography variant="h4">Purchased Augmentations</Typography>
<Typography>This faction also offers these augmentations but you already own them.</Typography>
{owned.map((aug) => purchaseableAugmentation(aug, true))}
</>
);
}
const multiplierComponent = const multiplierComponent =
props.faction.name !== FactionNames.ShadowsOfAnarchy ? ( props.faction.name !== FactionNames.ShadowsOfAnarchy ? (
<Typography> <Typography>
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())} <b>Price multiplier:</b> x {numeralWrapper.formatReallyBigNumber(getGenericAugmentationPriceMultiplier())}
</Typography> </Typography>
) : ( ) : (
<></> <></>
@ -177,42 +143,78 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Button onClick={props.routeToMainPage}>Back</Button> <Button onClick={props.routeToMainPage}>Back</Button>
<Typography variant="h4">Faction Augmentations</Typography> <Typography variant="h4">Faction Augmentations</Typography>
<Paper sx={{ p: 1, mb: 1 }}>
<Typography> <Typography>
These are all of the Augmentations that are available to purchase from {props.faction.name}. Augmentations are These are all of the Augmentations that are available to purchase from <b>{props.faction.name}</b>.
powerful upgrades that will enhance your abilities. Augmentations are powerful upgrades that will enhance your abilities.
<br /> <br />
Reputation: <Reputation reputation={props.faction.playerReputation} /> Favor:{" "}
<Favor favor={Math.floor(props.faction.favor)} />
</Typography> </Typography>
<Box display="flex"> <Box
sx={{
display: "grid",
gridTemplateColumns: `repeat(${props.faction.name === FactionNames.ShadowsOfAnarchy ? "2" : "3"}, 1fr)`,
justifyItems: "center",
my: 1,
}}
>
<Tooltip <Tooltip
title={ title={
<Typography> <Typography>
The price of every Augmentation increases for every queued Augmentation and it is reset when you install The price of every Augmentation increases for every queued Augmentation and it is reset when you
them. install them.
</Typography> </Typography>
} }
> >
{multiplierComponent} {multiplierComponent}
</Tooltip> </Tooltip>
<Typography>
<b>Reputation:</b> <Reputation reputation={props.faction.playerReputation} />
</Typography>
<Typography>
<b>Favor:</b> <Favor favor={Math.floor(props.faction.favor)} />
</Typography>
</Box> </Box>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)" }}>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button> <Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}>Sort by Reputation</Button> <Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>Sort by Default Order</Button> Sort by Reputation
</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>
Sort by Default Order
</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Purchasable)}> <Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Purchasable)}>
Sort by Purchasable Sort by Purchasable
</Button> </Button>
<br /> </Box>
</Paper>
</Container>
<Table size="small" padding="none"> <PurchasableAugmentations
<TableBody>{augListElems}</TableBody> augNames={purchasable}
</Table> ownedAugNames={owned}
player={player}
<Table size="small" padding="none"> canPurchase={(player, aug) => {
<TableBody>{ownedElem}</TableBody> const augCost = aug.getCost(player).moneyCost;
</Table> return (
hasAugmentationPrereqs(aug) &&
props.faction.playerReputation >= aug.baseRepRequirement &&
(augCost === 0 || player.money > augCost)
);
}}
purchaseAugmentation={(player, aug, showModal) => {
if (!Settings.SuppressBuyAugmentationConfirmation) {
showModal(true);
} else {
purchaseAugmentation(aug, props.faction);
rerender();
}
}}
rep={props.faction.playerReputation}
faction={props.faction}
/>
</> </>
); );
} }

@ -5,7 +5,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers"; import { hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations"; import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
@ -23,6 +22,7 @@ import Box from "@mui/material/Box";
import { TableCell } from "../../ui/React/Table"; import { TableCell } from "../../ui/React/Table";
import TableRow from "@mui/material/TableRow"; import TableRow from "@mui/material/TableRow";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { PurchaseAugmentationModal } from "../../Augmentation/ui/PurchaseAugmentationModal";
interface IReqProps { interface IReqProps {
augName: string; augName: string;
@ -139,13 +139,7 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
<Button onClick={handleClick} color={color}> <Button onClick={handleClick} color={color}>
Buy Buy
</Button> </Button>
<PurchaseAugmentationModal <PurchaseAugmentationModal open={open} onClose={() => setOpen(false)} aug={aug} faction={props.faction} />
open={open}
onClose={() => setOpen(false)}
aug={aug}
faction={props.faction}
rerender={props.rerender}
/>
</TableCell> </TableCell>
)} )}
<TableCell key={1}> <TableCell key={1}>

@ -31,9 +31,9 @@ export function calculateTradeInformationRepReward(
const levelBonus = maxLevel * Math.pow(1.01, maxLevel); const levelBonus = maxLevel * Math.pow(1.01, maxLevel);
return ( return (
Math.pow(reward + 1, 2) * Math.pow(reward + 1, 1.1) *
Math.pow(difficulty, 3) * Math.pow(difficulty, 1.2) *
3e3 * 30 *
levelBonus * levelBonus *
(player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.5 : 1) * (player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney BitNodeMultipliers.InfiltrationMoney

@ -1,5 +1,4 @@
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations"; import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { GraftableAugmentation } from "./GraftableAugmentation"; import { GraftableAugmentation } from "./GraftableAugmentation";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
@ -7,8 +6,7 @@ export const getGraftingAvailableAugs = (player: IPlayer): string[] => {
const augs: string[] = []; const augs: string[] = [];
for (const [augName, aug] of Object.entries(StaticAugmentations)) { for (const [augName, aug] of Object.entries(StaticAugmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor || augName === AugmentationNames.TheRedPill || aug.isSpecial) if (aug.isSpecial) continue;
continue;
augs.push(augName); augs.push(augName);
} }

@ -140,6 +140,7 @@ export const GraftingRoot = (): React.ReactElement => {
</> </>
} }
/> />
<Box sx={{ maxHeight: 330, overflowY: "scroll" }}>
<Typography color={Settings.theme.info}> <Typography color={Settings.theme.info}>
<b>Time to Graft:</b>{" "} <b>Time to Graft:</b>{" "}
{convertTimeMsToTimeElapsedString( {convertTimeMsToTimeElapsedString(
@ -147,12 +148,14 @@ export const GraftingRoot = (): React.ReactElement => {
)} )}
{/* Use formula so the displayed creation time is accurate to player bonus */} {/* Use formula so the displayed creation time is accurate to player bonus */}
</Typography> </Typography>
{selectedAugmentation.prereqs.length > 0 && ( {selectedAugmentation.prereqs.length > 0 && (
<AugPreReqsChecklist player={player} aug={selectedAugmentation} /> <AugPreReqsChecklist player={player} aug={selectedAugmentation} />
)} )}
<br /> <br />
<Typography sx={{ maxHeight: 305, overflowY: "scroll" }}>
<Typography>
{(() => { {(() => {
const info = const info =
typeof selectedAugmentation.info === "string" ? ( typeof selectedAugmentation.info === "string" ? (
@ -172,6 +175,7 @@ export const GraftingRoot = (): React.ReactElement => {
})()} })()}
</Typography> </Typography>
</Box> </Box>
</Box>
</Paper> </Paper>
) : ( ) : (
<Typography>All Augmentations owned</Typography> <Typography>All Augmentations owned</Typography>

@ -2,9 +2,7 @@ import { Factions } from "../../Faction/Factions";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { Gang } from "../../Gang/Gang"; import { Gang } from "../../Gang/Gang";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
import { GangConstants } from "../../Gang/data/Constants" import { GangConstants } from "../../Gang/data/Constants";
export function canAccessGang(this: IPlayer): boolean { export function canAccessGang(this: IPlayer): boolean {
if (this.bitNodeN === 2) { if (this.bitNodeN === 2) {

@ -5,7 +5,6 @@ import { IPlayer } from "../IPlayer";
import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentation } from "../../Augmentation/Augmentation";
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations"; import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions"; 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 // Helper function that helps filter out augs that are already owned
// and augs that aren't allowed for sleeves // and augs that aren't allowed for sleeves
function isAvailableForSleeve(aug: Augmentation): boolean { function isAvailableForSleeve(aug: Augmentation): boolean {
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
return false;
}
if (ownedAugNames.includes(aug.name)) { if (ownedAugNames.includes(aug.name)) {
return false; return false;
} }

@ -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 { Sleeve } from "../Sleeve";
import { findSleevePurchasableAugs } from "../SleeveHelpers"; import { findSleevePurchasableAugs } from "../SleeveHelpers";
import { StaticAugmentations } from "../../../Augmentation/StaticAugmentations";
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 { interface IProps {
open: boolean; open: boolean;
@ -42,15 +32,9 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
// and you must also have enough rep in that faction in order to purchase it. // and you must also have enough rep in that faction in order to purchase it.
const availableAugs = findSleevePurchasableAugs(props.sleeve, player); const availableAugs = findSleevePurchasableAugs(props.sleeve, player);
function purchaseAugmentation(aug: Augmentation): void {
props.sleeve.tryBuyAugmentation(player, aug);
rerender();
}
return ( return (
<Modal open={props.open} onClose={props.onClose}> <Modal open={props.open} onClose={props.onClose}>
<> <Container component={Paper} disableGutters maxWidth="lg" sx={{ mx: 0, mb: 1, p: 1 }}>
<Box sx={{ mx: 1 }}>
<Typography> <Typography>
You can purchase Augmentations for your Duplicate Sleeves. These Augmentations have the same effect as they 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. would for you. You can only purchase Augmentations that you have unlocked through Factions.
@ -58,64 +42,24 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
<br /> <br />
When purchasing an Augmentation for a Duplicate Sleeve, they are immediately installed. This means that the 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. Duplicate Sleeve will immediately lose all of its stat experience.
<br />
<br />
Augmentations will appear below as they become available.
</Typography> </Typography>
<Box component={Paper} sx={{ my: 1, p: 1 }}> </Container>
<Table size="small" padding="none"> <PurchasableAugmentations
<TableBody> augNames={availableAugs.map((aug) => aug.name)}
{availableAugs.map((aug) => { ownedAugNames={ownedAugNames}
return ( player={player}
<TableRow key={aug.name}> canPurchase={(player, aug) => {
<TableCell> return player.money > aug.baseCost;
<Button onClick={() => purchaseAugmentation(aug)} disabled={player.money < aug.baseCost}> }}
Buy purchaseAugmentation={(player, aug, _showModal) => {
</Button> props.sleeve.tryBuyAugmentation(player, aug);
</TableCell> rerender();
<TableCell> }}
<Box display="flex"> sleeveAugs
<Tooltip title={aug.stats || ""}> />
<Typography>{aug.name}</Typography>
</Tooltip>
</Box>
</TableCell>
<TableCell>
<Money money={aug.baseCost} player={player} />
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Box>
</Box>
{ownedAugNames.length > 0 && (
<>
<Typography sx={{ mx: 1 }}>Owned Augmentations:</Typography>
<Box display="grid" sx={{ gridTemplateColumns: "repeat(5, 1fr)", m: 1 }}>
{ownedAugNames.map((augName) => {
const aug = StaticAugmentations[augName];
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return (
<Tooltip key={augName} title={<Typography>{tooltip}</Typography>}>
<Paper sx={{ p: 1 }}>
<Typography>{augName}</Typography>
</Paper>
</Tooltip>
);
})}
</Box>
</>
)}
</>
</Modal> </Modal>
); );
} }

@ -400,8 +400,19 @@ function evaluateVersionCompatibility(ver: string | number): void {
if (ver < 15) { if (ver < 15) {
(Settings as any).EditorTheme = { ...defaultMonacoTheme }; (Settings as any).EditorTheme = { ...defaultMonacoTheme };
} }
//Fix contract names
if (ver < 16) { if (ver < 16) {
Factions[FactionNames.ShadowsOfAnarchy] = new Faction(FactionNames.ShadowsOfAnarchy); 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";
}
}
}
} }
} }
} }

@ -1250,7 +1250,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
}, },
{ {
name: "HammingCodes: Integer to encoded Binary", name: "HammingCodes: Integer to Encoded Binary",
numTries: 10, numTries: 10,
difficulty: 5, difficulty: 5,
desc: (n: number): string => { desc: (n: number): string => {