Merge branch 'dev' into bugfix/corp-updates

This commit is contained in:
Jack
2022-04-13 16:35:18 +08:00
committed by GitHub
171 changed files with 5830 additions and 3619 deletions
+3 -4
View File
@@ -2,7 +2,6 @@
import * as React from "react";
import { IMap } from "../types";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Faction } from "../Faction/Faction";
import { Factions } from "../Faction/Factions";
import { numeralWrapper } from "../ui/numeralFormat";
@@ -134,7 +133,7 @@ function generateStatsDescription(mults: IMap<number>, programs?: string[], star
desc = (
<>
{desc}
<br />+{f(mults.charisma_mult - 1)} Charisma skill
<br />+{f(mults.charisma_mult - 1)} charisma skill
</>
);
}
@@ -410,8 +409,8 @@ export class Augmentation {
this.info = params.info;
this.prereqs = params.prereqs ? params.prereqs : [];
this.baseRepRequirement = params.repCost * BitNodeMultipliers.AugmentationRepCost;
this.baseCost = params.moneyCost * BitNodeMultipliers.AugmentationMoneyCost;
this.baseRepRequirement = params.repCost;
this.baseCost = params.moneyCost;
this.startingCost = this.baseCost;
this.factions = params.factions;
+19
View File
@@ -1484,6 +1484,25 @@ export const initGeneralAugmentations = (): Augmentation[] => [
),
factions: [FactionNames.TianDiHui],
}),
// Grafting-exclusive Augmentation
new Augmentation({
name: AugmentationNames.CongruityImplant,
repCost: Infinity,
moneyCost: 50e12,
info: (
<>
Developed by a pioneer in Grafting research, this implant generates pulses of stability which seem to have a
nullifying effect versus the Entropy virus.
<br />
<br />
<b>Note:</b> For unknown reasons, the lowercase <code>n</code> appears to be an integral component to its
functionality.
</>
),
stats: <>This Augmentation removes the Entropy virus, and prevents it from affecting you again.</>,
factions: [],
}),
];
export const initBladeburnerAugmentations = (): Augmentation[] => [
+18 -8
View File
@@ -21,6 +21,7 @@ import {
initNeuroFluxGovernor,
initUnstableCircadianModulator,
} from "./AugmentationCreator";
import { Router } from "../ui/GameRoot";
export function AddToAugmentations(aug: Augmentation): void {
const name = aug.name;
@@ -141,6 +142,12 @@ function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void
}
}
// Special logic for Congruity Implant
if (aug.name === AugmentationNames.CongruityImplant && !reapply) {
Player.entropy = 0;
Player.applyEntropy(Player.entropy);
}
// Push onto Player's Augmentation list
if (!reapply) {
const ownedAug = new PlayerOwnedAugmentation(aug.name);
@@ -148,8 +155,8 @@ function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void
}
}
function installAugmentations(): boolean {
if (Player.queuedAugmentations.length == 0) {
function installAugmentations(force?: boolean): boolean {
if (Player.queuedAugmentations.length == 0 && !force) {
dialogBoxCreate("You have not purchased any Augmentations to install!");
return false;
}
@@ -179,13 +186,16 @@ function installAugmentations(): boolean {
augmentationList += aug.name + level + "<br>";
}
Player.queuedAugmentations = [];
dialogBoxCreate(
"You slowly drift to sleep as scientists put you under in order " +
"to install the following Augmentations:<br>" +
augmentationList +
"<br>You wake up in your home...you feel different...",
);
if (!force) {
dialogBoxCreate(
"You slowly drift to sleep as scientists put you under in order " +
"to install the following Augmentations:<br>" +
augmentationList +
"<br>You wake up in your home...you feel different...",
);
}
prestigeAugmentation();
Router.toTerminal();
return true;
}
@@ -91,6 +91,7 @@ export enum AugmentationNames {
BionicArms = "Bionic Arms",
SNA = "Social Negotiation Assistant (S.N.A)",
HydroflameLeftArm = "Hydroflame Left Arm",
CongruityImplant = "nickofolas Congruity Implant",
EsperEyewear = "EsperTech Bladeburner Eyewear",
EMS4Recombination = "EMS-4 Recombination",
OrionShoulder = "ORION-MKIV Shoulder",
+151 -72
View File
@@ -7,7 +7,7 @@ import React, { useState, useEffect } from "react";
import { InstalledAugmentations } from "./InstalledAugmentations";
import { PlayerMultipliers } from "./PlayerMultipliers";
import { PurchasedAugmentations } from "./PurchasedAugmentations";
import { SourceFiles } from "./SourceFiles";
import { SourceFilesElement } from "./SourceFiles";
import { canGetBonus } from "../../ExportBonus";
import { use } from "../../ui/Context";
@@ -16,8 +16,55 @@ import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
import Container from "@mui/material/Container";
import { Settings } from "../../Settings/Settings";
import { ConfirmationModal } from "../../ui/React/ConfirmationModal";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { AugmentationNames } from "../data/AugmentationNames";
import { Augmentations } from "../Augmentations";
import { CONSTANTS } from "../../Constants";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { Info } from "@mui/icons-material";
interface NFGDisplayProps {
player: IPlayer;
}
const NeuroFluxDisplay = ({ player }: NFGDisplayProps): React.ReactElement => {
const level = player.augmentations.find((e) => e.name === AugmentationNames.NeuroFluxGovernor)?.level ?? 0;
return level > 0 ? (
<Paper sx={{ p: 1 }}>
<Typography variant="h5" color={Settings.theme.info}>
NeuroFlux Governor - Level {level}
</Typography>
<Typography color={Settings.theme.info}>{Augmentations[AugmentationNames.NeuroFluxGovernor].stats}</Typography>
</Paper>
) : (
<></>
);
};
interface EntropyDisplayProps {
player: IPlayer;
}
const EntropyDisplay = ({ player }: EntropyDisplayProps): React.ReactElement => {
return player.entropy > 0 ? (
<Paper sx={{ p: 1 }}>
<Typography variant="h5" color={Settings.theme.error}>
Entropy Virus - Level {player.entropy}
</Typography>
<Typography color={Settings.theme.error}>
<b>All multipliers decreased by:</b> {formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropy) * 100, 3)}%
(multiplicative)
</Typography>
</Paper>
) : (
<></>
);
};
interface IProps {
exportGameFn: () => void;
@@ -55,81 +102,113 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
}
return (
<>
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Typography variant="h4">Augmentations</Typography>
<Box mx={2}>
<Typography>
Below is a list of all Augmentations you have purchased but not yet installed. Click the button below to
install them.
</Typography>
<Typography>WARNING: Installing your Augmentations resets most of your progress, including:</Typography>
<br />
<Typography>- Stats/Skill levels and Experience</Typography>
<Typography>- Money</Typography>
<Typography>- Scripts on every computer but your home computer</Typography>
<Typography>- Purchased servers</Typography>
<Typography>- Hacknet Nodes</Typography>
<Typography>- Faction/Company reputation</Typography>
<Typography>- Stocks</Typography>
<br />
<Typography>
Installing Augmentations lets you start over with the perks and benefits granted by all of the Augmentations
you have ever installed. Also, you will keep any scripts and RAM/Core upgrades on your home computer (but you
will lose all programs besides NUKE.exe)
</Typography>
<Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5" color="primary" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
Purchased Augmentations
<Tooltip
title={
<>
<Typography>
Below is a list of all Augmentations you have purchased but not yet installed. Click the button
below to install them.
</Typography>
<Typography>
WARNING: Installing your Augmentations resets most of your progress, including:
</Typography>
<br />
<Typography>- Stats/Skill levels and Experience</Typography>
<Typography>- Money</Typography>
<Typography>- Scripts on every computer but your home computer</Typography>
<Typography>- Purchased servers</Typography>
<Typography>- Hacknet Nodes</Typography>
<Typography>- Faction/Company reputation</Typography>
<Typography>- Stocks</Typography>
<br />
<Typography>
Installing Augmentations lets you start over with the perks and benefits granted by all of the
Augmentations you have ever installed. Also, you will keep any scripts and RAM/Core upgrades on your
home computer (but you will lose all programs besides NUKE.exe)
</Typography>
</>
}
>
<Info sx={{ ml: 1, mb: 0.5 }} color="info" />
</Tooltip>
</Typography>
<ConfirmationModal
open={installOpen}
onClose={() => setInstallOpen(false)}
onConfirm={props.installAugmentationsFn}
confirmationText={
<>
Installing will reset
<br />
<br />- money
<br />- skill / experience
<br />- every server except home
<br />- factions and reputation
<br />
<br />
You will keep:
<br />
<br />- All scripts on home
<br />- home ram and cores
<br />
<br />
It is recommended to install several Augmentations at once. Preferably everything from any faction of
your choosing.
</>
}
/>
<Box sx={{ display: "grid", width: "100%", gridTemplateColumns: "1fr 1fr" }}>
<Tooltip title={<Typography>'I never asked for this'</Typography>}>
<span>
<Button sx={{ width: "100%" }} disabled={player.queuedAugmentations.length === 0} onClick={doInstall}>
Install Augmentations
</Button>
</span>
</Tooltip>
<Tooltip title={<Typography>It's always a good idea to backup/export your save!</Typography>}>
<Button sx={{ width: "100%" }} onClick={doExport} color="error">
Backup Save {exportBonusStr()}
</Button>
</Tooltip>
</Box>
</Paper>
{player.queuedAugmentations.length > 0 ? (
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<PurchasedAugmentations />
<PlayerMultipliers />
</Box>
) : (
<Paper sx={{ p: 1 }}>
<Typography>No Augmentations have been purchased yet</Typography>
</Paper>
)}
</Box>
<Typography variant="h4" color="primary">
Purchased Augmentations
</Typography>
<Box mx={2}>
<Tooltip title={<Typography>'I never asked for this'</Typography>}>
<span>
<Button disabled={player.queuedAugmentations.length === 0} onClick={doInstall}>
Install Augmentations
</Button>
</span>
</Tooltip>
<ConfirmationModal
open={installOpen}
onClose={() => setInstallOpen(false)}
onConfirm={props.installAugmentationsFn}
confirmationText={
<>
Installing will reset
<br />
<br />- money
<br />- skill / experience
<br />- every server except home
<br />- factions and reputation
<br />
<br />
You will keep:
<br />
<br />- All scripts on home
<br />- home ram and cores
<br />
<br />
It is recommended to install several Augmentations at once. Preferably everything from any faction of your
choosing.
</>
}
/>
<Tooltip title={<Typography>It's always a good idea to backup/export your save!</Typography>}>
<Button sx={{ mx: 2 }} onClick={doExport} color="error">
Backup Save {exportBonusStr()}
</Button>
</Tooltip>
<PurchasedAugmentations />
<Box
sx={{
my: 1,
display: "grid",
gridTemplateColumns: `repeat(${
+!!((player.augmentations.find((e) => e.name === AugmentationNames.NeuroFluxGovernor)?.level ?? 0) > 0) +
+!!(player.entropy > 0)
}, 1fr)`,
gap: 1,
}}
>
<NeuroFluxDisplay player={player} />
<EntropyDisplay player={player} />
</Box>
<Typography variant="h4">Installed Augmentations</Typography>
<Box mx={2}>
<Typography>
List of all Augmentations that have been installed. You have gained the effects of these.
</Typography>
<Box>
<InstalledAugmentations />
</Box>
<PlayerMultipliers />
<SourceFiles />
</>
<SourceFilesElement />
</Container>
);
}
+62 -66
View File
@@ -5,28 +5,23 @@
* It also contains 'configuration' buttons that allow you to change how the
* Augs/SF's are displayed
*/
import { Box, ListItemButton, Paper, Typography } from "@mui/material";
import Button from "@mui/material/Button";
import List from "@mui/material/List";
import Tooltip from "@mui/material/Tooltip";
import React, { useState } from "react";
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
import { Augmentations } from "../Augmentations";
import { AugmentationNames } from "../data/AugmentationNames";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import List from "@mui/material/List";
import { ExpandLess, ExpandMore } from "@mui/icons-material";
import { Box, Paper, ListItemButton, ListItemText, Typography, Collapse } from "@mui/material";
import { CONSTANTS } from "../../Constants";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { Augmentations } from "../Augmentations";
import { AugmentationNames } from "../data/AugmentationNames";
export function InstalledAugmentations(): React.ReactElement {
const setRerender = useState(true)[1];
const player = use.Player();
const sourceAugs = player.augmentations.slice().filter((aug) => aug.name !== AugmentationNames.NeuroFluxGovernor);
const sourceAugs = player.augmentations.slice();
const [selectedAug, setSelectedAug] = useState(sourceAugs[0]);
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
sourceAugs.sort((aug1, aug2) => {
@@ -49,59 +44,60 @@ export function InstalledAugmentations(): React.ReactElement {
}
return (
<>
<Tooltip title={"Sorts the Augmentations alphabetically in numeral order"}>
<Button onClick={sortInOrder}>Sort in Order</Button>
</Tooltip>
<Tooltip title={"Sorts the Augmentations based on when you acquired them (same as default)"}>
<Button sx={{ mx: 2 }} onClick={sortByAcquirementTime}>
Sort by Acquirement Time
</Button>
</Tooltip>
<List dense>
{player.entropy > 0 &&
(() => {
const [open, setOpen] = useState(false);
return (
<Box component={Paper}>
<ListItemButton onClick={() => setOpen((old) => !old)}>
<ListItemText
primary={
<Typography color={Settings.theme.hp} style={{ whiteSpace: "pre-wrap" }}>
Entropy Virus - Level {player.entropy}
</Typography>
}
/>
{open ? (
<ExpandLess sx={{ color: Settings.theme.hp }} />
) : (
<ExpandMore sx={{ color: Settings.theme.hp }} />
)}
<Box sx={{ width: "100%" }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Installed Augmentations</Typography>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Tooltip title={"Sorts the Augmentations alphabetically in numeral order"}>
<Button sx={{ width: "100%" }} onClick={sortInOrder}>
Sort in Order
</Button>
</Tooltip>
<Tooltip title={"Sorts the Augmentations based on when you acquired them (same as default)"}>
<Button sx={{ width: "100%" }} onClick={sortByAcquirementTime}>
Sort by Time of Acquirement
</Button>
</Tooltip>
</Box>
</Paper>
{sourceAugs.length > 0 ? (
<Paper sx={{ display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<Box>
<List sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}>
{sourceAugs.map((k, i) => (
<ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
<Typography>{k.name}</Typography>
</ListItemButton>
<Collapse in={open} unmountOnExit>
<Box m={4}>
<Typography color={Settings.theme.hp}>
<b>All multipliers decreased by:</b>{" "}
{formatNumber((1 - CONSTANTS.EntropyEffect ** player.entropy) * 100, 3)}% (multiplicative)
</Typography>
</Box>
</Collapse>
</Box>
);
})()}
))}
</List>
</Box>
<Box sx={{ m: 1 }}>
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
{selectedAug.name}
</Typography>
<Typography sx={{ maxHeight: 350, overflowY: "scroll" }}>
{(() => {
const aug = Augmentations[selectedAug.name];
{sourceAugs.map((e) => {
const aug = Augmentations[e.name];
let level = null;
if (e.name === AugmentationNames.NeuroFluxGovernor) {
level = e.level;
}
return <AugmentationAccordion key={aug.name} aug={aug} level={level} />;
})}
</List>
</>
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return tooltip;
})()}
</Typography>
</Box>
</Paper>
) : (
<Paper sx={{ p: 1 }}>
<Typography>No Augmentations have been installed yet</Typography>
</Paper>
)}
</Box>
);
}
+239 -256
View File
@@ -1,20 +1,22 @@
/**
* React component for displaying the player's multipliers on the Augmentation UI page
*/
import { DoubleArrow } from "@mui/icons-material";
import { List, ListItem, ListItemText, Paper, Typography } from "@mui/material";
import * as React from "react";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentations } from "../Augmentations";
import { Table, TableCell } from "../../ui/React/Table";
import TableBody from "@mui/material/TableBody";
import TableRow from "@mui/material/TableRow";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
function calculateAugmentedStats(): any {
const augP: any = {};
interface IAugmentedStats {
[index: string]: number;
}
function calculateAugmentedStats(): IAugmentedStats {
const augP: IAugmentedStats = {};
for (const aug of Player.queuedAugmentations) {
const augObj = Augmentations[aug.name];
for (const mult of Object.keys(augObj.mults)) {
@@ -25,268 +27,249 @@ function calculateAugmentedStats(): any {
return augP;
}
function Improvements({ r, m }: { r: number; m: number }): React.ReactElement {
if (r) {
return (
<>
<TableCell key="2">
<Typography>&nbsp;{"=>"}&nbsp;</Typography>
</TableCell>
<TableCell key="3">
<Typography>
{numeralWrapper.formatPercentage(r)} <BN5Stat base={r} mult={m} />
</Typography>
</TableCell>
</>
);
}
return <></>;
}
interface IBN5StatsProps {
interface IBitNodeModifiedStatsProps {
base: number;
mult: number;
color: string;
}
function BN5Stat(props: IBN5StatsProps): React.ReactElement {
if (props.mult === 1) return <></>;
return <>({numeralWrapper.formatPercentage(props.base * props.mult)})</>;
}
function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactElement {
// If player doesn't have SF5 or if the property isn't affected by BitNode mults
if (props.mult === 1 || SourceFileFlags[5] === 0)
return <Typography color={props.color}>{numeralWrapper.formatPercentage(props.base)}</Typography>;
function MultiplierTable({ rows }: { rows: [string, number, number, number][] }): React.ReactElement {
return (
<Table size="small" padding="none">
<TableBody>
{rows.map((r: any) => (
<TableRow key={r[0]}>
<TableCell key="0">
<Typography noWrap>{r[0]} multiplier:&nbsp;</Typography>
</TableCell>
<TableCell key="1" style={{ textAlign: "right" }}>
<Typography noWrap>
{numeralWrapper.formatPercentage(r[1])} <BN5Stat base={r[1]} mult={r[3]} />
</Typography>
</TableCell>
<Improvements r={r[2]} m={r[3]} />
</TableRow>
))}
</TableBody>
</Table>
<Typography color={props.color}>
<span style={{ opacity: 0.5 }}>{numeralWrapper.formatPercentage(props.base)}</span>{" "}
{numeralWrapper.formatPercentage(props.base * props.mult)}
</Typography>
);
}
type MultiplierListItemData = [
multiplier: string,
currentValue: number,
augmentedValue: number,
bitNodeMultiplier: number,
color: string,
];
interface IMultiplierListProps {
rows: MultiplierListItemData[];
}
function MultiplierList(props: IMultiplierListProps): React.ReactElement {
const listItems = props.rows
.map((data) => {
const [multiplier, currentValue, augmentedValue, bitNodeMultiplier, color] = data;
if (!isNaN(augmentedValue)) {
return (
<ListItem key={multiplier} disableGutters sx={{ py: 0 }}>
<ListItemText
sx={{ my: 0.1 }}
primary={
<Typography color={color}>
<b>{multiplier}</b>
</Typography>
}
secondary={
<span style={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
<BitNodeModifiedStats base={currentValue} mult={bitNodeMultiplier} color={color} />
<DoubleArrow fontSize="small" color="success" sx={{ mb: 0.5, mx: 1 }} />
<BitNodeModifiedStats base={augmentedValue} mult={bitNodeMultiplier} color={Settings.theme.success} />
</span>
}
disableTypography
/>
</ListItem>
);
}
return;
})
.filter((i) => i !== undefined);
return listItems.length > 0 ? <List disablePadding>{listItems}</List> : <></>;
}
export function PlayerMultipliers(): React.ReactElement {
const mults = calculateAugmentedStats();
function BladeburnerMults(): React.ReactElement {
if (!Player.canAccessBladeburner()) return <></>;
return (
<>
<MultiplierTable
rows={[
[
"Bladeburner Success Chance",
Player.bladeburner_success_chance_mult,
Player.bladeburner_success_chance_mult * mults.bladeburner_success_chance_mult,
1,
],
[
"Bladeburner Max Stamina",
Player.bladeburner_max_stamina_mult,
Player.bladeburner_max_stamina_mult * mults.bladeburner_max_stamina_mult,
1,
],
[
"Bladeburner Stamina Gain",
Player.bladeburner_stamina_gain_mult,
Player.bladeburner_stamina_gain_mult * mults.bladeburner_stamina_gain_mult,
1,
],
[
"Bladeburner Field Analysis",
Player.bladeburner_analysis_mult,
Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult,
1,
],
]}
/>
<br />
</>
// Column data is a bit janky, so it's set up here to allow for
// easier logic in setting up the layout
const leftColData: MultiplierListItemData[] = [
...[
["Hacking Chance ", Player.hacking_chance_mult, Player.hacking_chance_mult * mults.hacking_chance_mult, 1],
["Hacking Speed ", Player.hacking_speed_mult, Player.hacking_speed_mult * mults.hacking_speed_mult, 1],
["Hacking Money ", Player.hacking_money_mult, Player.hacking_money_mult * mults.hacking_money_mult, 1],
["Hacking Growth ", Player.hacking_grow_mult, Player.hacking_grow_mult * mults.hacking_grow_mult, 1],
[
"Hacking Level ",
Player.hacking_mult,
Player.hacking_mult * mults.hacking_mult,
BitNodeMultipliers.HackingLevelMultiplier,
],
[
"Hacking Experience ",
Player.hacking_exp_mult,
Player.hacking_exp_mult * mults.hacking_exp_mult,
BitNodeMultipliers.HackExpGain,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.hack])),
...[
[
"Strength Level ",
Player.strength_mult,
Player.strength_mult * mults.strength_mult,
BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience ", Player.strength_exp_mult, Player.strength_exp_mult * mults.strength_exp_mult, 1],
[
"Defense Level ",
Player.defense_mult,
Player.defense_mult * mults.defense_mult,
BitNodeMultipliers.DefenseLevelMultiplier,
],
["Defense Experience ", Player.defense_exp_mult, Player.defense_exp_mult * mults.defense_exp_mult, 1],
[
"Dexterity Level ",
Player.dexterity_mult,
Player.dexterity_mult * mults.dexterity_mult,
BitNodeMultipliers.DexterityLevelMultiplier,
],
["Dexterity Experience ", Player.dexterity_exp_mult, Player.dexterity_exp_mult * mults.dexterity_exp_mult, 1],
[
"Agility Level ",
Player.agility_mult,
Player.agility_mult * mults.agility_mult,
BitNodeMultipliers.AgilityLevelMultiplier,
],
["Agility Experience ", Player.agility_exp_mult, Player.agility_exp_mult * mults.agility_exp_mult, 1],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.combat])),
[
"Charisma Level ",
Player.charisma_mult,
Player.charisma_mult * mults.charisma_mult,
BitNodeMultipliers.CharismaLevelMultiplier,
Settings.theme.cha,
],
[
"Charisma Experience ",
Player.charisma_exp_mult,
Player.charisma_exp_mult * mults.charisma_exp_mult,
1,
Settings.theme.cha,
],
];
const rightColData: MultiplierListItemData[] = [
...[
[
"Hacknet Node production ",
Player.hacknet_node_money_mult,
Player.hacknet_node_money_mult * mults.hacknet_node_money_mult,
BitNodeMultipliers.HacknetNodeMoney,
],
[
"Hacknet Node purchase cost ",
Player.hacknet_node_purchase_cost_mult,
Player.hacknet_node_purchase_cost_mult * mults.hacknet_node_purchase_cost_mult,
1,
],
[
"Hacknet Node RAM upgrade cost ",
Player.hacknet_node_ram_cost_mult,
Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult,
1,
],
[
"Hacknet Node Core purchase cost ",
Player.hacknet_node_core_cost_mult,
Player.hacknet_node_core_cost_mult * mults.hacknet_node_core_cost_mult,
1,
],
[
"Hacknet Node level upgrade cost ",
Player.hacknet_node_level_cost_mult,
Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult,
1,
],
["Company reputation gain ", Player.company_rep_mult, Player.company_rep_mult * mults.company_rep_mult, 1],
[
"Faction reputation gain ",
Player.faction_rep_mult,
Player.faction_rep_mult * mults.faction_rep_mult,
BitNodeMultipliers.FactionWorkRepGain,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.primary])),
[
"Salary ",
Player.work_money_mult,
Player.work_money_mult * mults.work_money_mult,
BitNodeMultipliers.CompanyWorkMoney,
Settings.theme.money,
],
[
"Crime success ",
Player.crime_success_mult,
Player.crime_success_mult * mults.crime_success_mult,
1,
Settings.theme.combat,
],
[
"Crime money ",
Player.crime_money_mult,
Player.crime_money_mult * mults.crime_money_mult,
BitNodeMultipliers.CrimeMoney,
Settings.theme.money,
],
];
if (Player.canAccessBladeburner()) {
rightColData.push(
...[
[
"Bladeburner Success Chance",
Player.bladeburner_success_chance_mult,
Player.bladeburner_success_chance_mult * mults.bladeburner_success_chance_mult,
1,
],
[
"Bladeburner Max Stamina",
Player.bladeburner_max_stamina_mult,
Player.bladeburner_max_stamina_mult * mults.bladeburner_max_stamina_mult,
1,
],
[
"Bladeburner Stamina Gain",
Player.bladeburner_stamina_gain_mult,
Player.bladeburner_stamina_gain_mult * mults.bladeburner_stamina_gain_mult,
1,
],
[
"Bladeburner Field Analysis",
Player.bladeburner_analysis_mult,
Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult,
1,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.primary])),
);
}
const hasLeftImprovements = +!!(leftColData.filter((item) => item[2] !== 0).length > 0),
hasRightImprovements = +!!(rightColData.filter((item) => item[2] !== 0).length > 0);
return (
<>
<Typography variant="h4">Multipliers</Typography>
<Box mx={2}>
<MultiplierTable
rows={[
["Hacking Chance ", Player.hacking_chance_mult, Player.hacking_chance_mult * mults.hacking_chance_mult, 1],
["Hacking Speed ", Player.hacking_speed_mult, Player.hacking_speed_mult * mults.hacking_speed_mult, 1],
["Hacking Money ", Player.hacking_money_mult, Player.hacking_money_mult * mults.hacking_money_mult, 1],
["Hacking Growth ", Player.hacking_grow_mult, Player.hacking_grow_mult * mults.hacking_grow_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Hacking Level ",
Player.hacking_mult,
Player.hacking_mult * mults.hacking_mult,
BitNodeMultipliers.HackingLevelMultiplier,
],
[
"Hacking Experience ",
Player.hacking_exp_mult,
Player.hacking_exp_mult * mults.hacking_exp_mult,
BitNodeMultipliers.HackExpGain,
],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Strength Level ",
Player.strength_mult,
Player.strength_mult * mults.strength_mult,
BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience ", Player.strength_exp_mult, Player.strength_exp_mult * mults.strength_exp_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Defense Level ",
Player.defense_mult,
Player.defense_mult * mults.defense_mult,
BitNodeMultipliers.DefenseLevelMultiplier,
],
["Defense Experience ", Player.defense_exp_mult, Player.defense_exp_mult * mults.defense_exp_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Dexterity Level ",
Player.dexterity_mult,
Player.dexterity_mult * mults.dexterity_mult,
BitNodeMultipliers.DexterityLevelMultiplier,
],
[
"Dexterity Experience ",
Player.dexterity_exp_mult,
Player.dexterity_exp_mult * mults.dexterity_exp_mult,
1,
],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Agility Level ",
Player.agility_mult,
Player.agility_mult * mults.agility_mult,
BitNodeMultipliers.AgilityLevelMultiplier,
],
["Agility Experience ", Player.agility_exp_mult, Player.agility_exp_mult * mults.agility_exp_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Charisma Level ",
Player.charisma_mult,
Player.charisma_mult * mults.charisma_mult,
BitNodeMultipliers.CharismaLevelMultiplier,
],
["Charisma Experience ", Player.charisma_exp_mult, Player.charisma_exp_mult * mults.charisma_exp_mult, 1],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Hacknet Node production ",
Player.hacknet_node_money_mult,
Player.hacknet_node_money_mult * mults.hacknet_node_money_mult,
BitNodeMultipliers.HacknetNodeMoney,
],
[
"Hacknet Node purchase cost ",
Player.hacknet_node_purchase_cost_mult,
Player.hacknet_node_purchase_cost_mult * mults.hacknet_node_purchase_cost_mult,
1,
],
[
"Hacknet Node RAM upgrade cost ",
Player.hacknet_node_ram_cost_mult,
Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult,
1,
],
[
"Hacknet Node Core purchase cost ",
Player.hacknet_node_core_cost_mult,
Player.hacknet_node_core_cost_mult * mults.hacknet_node_core_cost_mult,
1,
],
[
"Hacknet Node level upgrade cost ",
Player.hacknet_node_level_cost_mult,
Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult,
1,
],
]}
/>
<br />
<MultiplierTable
rows={[
["Company reputation gain ", Player.company_rep_mult, Player.company_rep_mult * mults.company_rep_mult, 1],
[
"Faction reputation gain ",
Player.faction_rep_mult,
Player.faction_rep_mult * mults.faction_rep_mult,
BitNodeMultipliers.FactionWorkRepGain,
],
[
"Salary ",
Player.work_money_mult,
Player.work_money_mult * mults.work_money_mult,
BitNodeMultipliers.CompanyWorkMoney,
],
]}
/>
<br />
<MultiplierTable
rows={[
["Crime success ", Player.crime_success_mult, Player.crime_success_mult * mults.crime_success_mult, 1],
[
"Crime money ",
Player.crime_money_mult,
Player.crime_money_mult * mults.crime_money_mult,
BitNodeMultipliers.CrimeMoney,
],
]}
/>
<br />
<BladeburnerMults />
</Box>
</>
<Paper
sx={{
p: 1,
maxHeight: 400,
overflowY: "scroll",
display: "grid",
gridTemplateColumns: `repeat(${hasLeftImprovements + hasRightImprovements}, 1fr)`,
}}
>
<MultiplierList rows={leftColData} />
<MultiplierList rows={rightColData} />
</Paper>
);
}
+38 -7
View File
@@ -2,14 +2,11 @@
* React component for displaying all of the player's purchased (but not installed)
* Augmentations on the Augmentations UI.
*/
import { List, ListItemText, Paper, Tooltip, Typography } from "@mui/material";
import * as React from "react";
import { Player } from "../../Player";
import { Augmentations } from "../Augmentations";
import { AugmentationNames } from "../data/AugmentationNames";
import { Player } from "../../Player";
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
import List from "@mui/material/List";
export function PurchasedAugmentations(): React.ReactElement {
const augs: React.ReactElement[] = [];
@@ -23,14 +20,48 @@ export function PurchasedAugmentations(): React.ReactElement {
}
for (let i = 0; i < Player.queuedAugmentations.length; i++) {
const ownedAug = Player.queuedAugmentations[i];
let displayName = ownedAug.name;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) continue;
const aug = Augmentations[ownedAug.name];
let level = null;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {
level = ownedAug.level;
displayName += ` - Level ${level}`;
}
augs.push(<AugmentationAccordion key={aug.name} aug={aug} level={level} />);
augs.push(
<Tooltip
title={
<Typography>
{(() => {
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return tooltip;
})()}
</Typography>
}
enterNextDelay={500}
key={displayName}
>
<ListItemText sx={{ px: 2, py: 1 }} primary={displayName} />
</Tooltip>,
);
}
return <List dense>{augs}</List>;
return (
<Paper sx={{ py: 1, maxHeight: 400, overflowY: "scroll" }}>
<List sx={{ height: 400, overflowY: "scroll" }} disablePadding>
{augs}
</List>
</Paper>
);
}
+154 -16
View File
@@ -1,21 +1,159 @@
import React from "react";
import { SourceFileMinus1 } from "./SourceFileMinus1";
import { OwnedSourceFiles } from "./OwnedSourceFiles";
import List from "@mui/material/List";
import Typography from "@mui/material/Typography";
import { ListItemButton, ListItemText, Paper } from "@mui/material";
import Box from "@mui/material/Box";
import List from "@mui/material/List";
import Typography from "@mui/material/Typography";
import React, { useState } from "react";
import { Exploit, ExploitName } from "../../Exploits/Exploit";
import { Player } from "../../Player";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { SourceFile } from "../../SourceFile/SourceFile";
import { SourceFiles } from "../../SourceFile/SourceFiles";
interface SfMinus1 {
info: React.ReactElement;
n: number;
name: string;
lvl: number;
}
const safeGetSf = (sfNum: number): SourceFile | SfMinus1 | null => {
if (sfNum === -1) {
const sfMinus1: SfMinus1 = {
info: (
<>
This Source-File can only be acquired with obscure knowledge of the game, javascript, and the web ecosystem.
<br />
<br />
It increases all of the player's multipliers by 0.1%
<br />
<br />
You have found the following exploits:
<br />
<br />
{Player.exploits.map((c: Exploit) => (
<React.Fragment key={c}>
* {ExploitName(c)}
<br />
</React.Fragment>
))}
</>
),
lvl: Player.exploits.length,
n: -1,
name: "Source-File -1: Exploits in the BitNodes",
};
return sfMinus1;
}
const srcFileKey = "SourceFile" + sfNum;
const sfObj = SourceFiles[srcFileKey];
if (sfObj == null) {
console.error(`Invalid source file number: ${sfNum}`);
return null;
}
return sfObj;
};
const getMaxLevel = (sfObj: SourceFile | SfMinus1): string | number => {
let maxLevel;
switch (sfObj.n) {
case 12:
maxLevel = "∞";
break;
case -1:
maxLevel = Object.keys(Exploit).length;
break;
default:
maxLevel = "3";
}
return maxLevel;
};
export function SourceFilesElement(): React.ReactElement {
const sourceSfs = Player.sourceFiles.slice();
const exploits = Player.exploits;
// Create a fake SF for -1, if "owned"
if (exploits.length > 0) {
sourceSfs.unshift({
n: -1,
lvl: exploits.length,
});
}
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
sourceSfs.sort((sf1, sf2) => {
return sf1.n - sf2.n;
});
}
if (sourceSfs.length === 0) {
return <></>;
}
const [selectedSf, setSelectedSf] = useState(sourceSfs[0]);
export function SourceFiles(): React.ReactElement {
return (
<>
<Typography variant="h4">Source Files</Typography>
<Box mx={2}>
<List dense>
<SourceFileMinus1 />
<OwnedSourceFiles />
</List>
</Box>
</>
<Box sx={{ width: "100%", mt: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Source Files</Typography>
</Paper>
<Paper sx={{ display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<Box>
<List
sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}
disablePadding
>
{sourceSfs.map((e, i) => {
const sfObj = safeGetSf(e.n);
if (!sfObj) return;
const maxLevel = getMaxLevel(sfObj);
return (
<ListItemButton
key={i + 1}
onClick={() => setSelectedSf(e)}
selected={selectedSf.n === e.n}
sx={{ py: 0 }}
>
<ListItemText
disableTypography
primary={<Typography>{sfObj.name}</Typography>}
secondary={
<Typography>
Level {e.lvl} / {maxLevel}
</Typography>
}
/>
</ListItemButton>
);
})}
</List>
</Box>
<Box sx={{ m: 1 }}>
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
{safeGetSf(selectedSf.n)?.name}
</Typography>
<Typography sx={{ maxHeight: 350, overflowY: "scroll" }}>
{(() => {
const sfObj = safeGetSf(selectedSf.n);
if (!sfObj) return;
const maxLevel = getMaxLevel(sfObj);
return (
<>
Level {selectedSf.lvl} / {maxLevel}
<br />
<br />
{sfObj.info}
</>
);
})()}
</Typography>
</Box>
</Paper>
</Box>
);
}
+1 -2
View File
@@ -2,7 +2,7 @@ import React, { useState } from "react";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { IRouter } from "../../ui/Router";
import { BitNodes } from "../BitNode";
import { enterBitNode, setRedPillFlag } from "../../RedPill";
import { enterBitNode } from "../../RedPill";
import { PortalModal } from "./PortalModal";
import { CinematicText } from "../../ui/React/CinematicText";
import { use } from "../../ui/Context";
@@ -123,7 +123,6 @@ interface IProps {
}
export function BitverseRoot(props: IProps): React.ReactElement {
setRedPillFlag(true);
const player = use.Player();
const enter = enterBitNode;
const destroyed = player.bitNodeN;
+1 -1
View File
@@ -276,7 +276,7 @@ export const CONSTANTS: {
CodingContractBaseCompanyRepGain: 4000,
CodingContractBaseMoneyGain: 75e6,
// Augmentation crafting multipliers
// Augmentation grafting multipliers
AugmentationGraftingCostMult: 3,
AugmentationGraftingTimeBase: 3600000,
+3 -3
View File
@@ -161,9 +161,9 @@ export class Material {
break;
case "Robots":
this.dmd = 90;
this.dmdR = [80, 9];
this.dmdR = [80, 99];
this.cmp = 90;
this.cmpR = [80, 9];
this.cmpR = [80, 99];
this.bCost = 75e3;
this.mv = 0.5; //Less mv bc its processed twice
this.mku = 1;
@@ -172,7 +172,7 @@ export class Material {
this.dmd = 90;
this.dmdR = [80, 99];
this.cmp = 90;
this.cmpR = [80, 9];
this.cmpR = [80, 99];
this.bCost = 15e3;
this.mv = 0.8; //Less mv bc its processed twice
this.mku = 0.5;
+25
View File
@@ -29,6 +29,16 @@ export class OfficeSpace {
[EmployeePositions.RandD]: 0,
total: 0,
};
employeeJobs: { [key: string]: number } = {
[EmployeePositions.Operations]: 0,
[EmployeePositions.Engineer]: 0,
[EmployeePositions.Business]: 0,
[EmployeePositions.Management]: 0,
[EmployeePositions.RandD]: 0,
[EmployeePositions.Training]: 0,
[EmployeePositions.Unassigned]: 0,
total: 0,
};
constructor(params: IParams = {}) {
this.loc = params.loc ? params.loc : "";
@@ -48,6 +58,8 @@ export class OfficeSpace {
}
}
this.calculateTotalEmployees();
// Process Office properties
this.maxEne = 100;
this.maxHap = 100;
@@ -101,6 +113,19 @@ export class OfficeSpace {
return salaryPaid;
}
calculateTotalEmployees(): void {
//Reset
for (const name of Object.keys(this.employeeJobs)) {
this.employeeJobs[name] = 0;
}
for (let i = 0; i < this.employees.length; ++i) {
const employee = this.employees[i];
this.employeeJobs[employee.pos]++;
}
this.employeeJobs.total = this.employees.length;
}
calculateEmployeeProductivity(corporation: ICorporation, industry: IIndustry): void {
//Reset
for (const name of Object.keys(this.employeeProd)) {
+1 -1
View File
@@ -30,7 +30,7 @@ export function CityTabs(props: IProps): React.ReactElement {
}
return (
<>
<Tabs variant="fullWidth" value={city} onChange={handleChange} sx={{ maxWidth: "65%" }}>
<Tabs variant="fullWidth" value={city} onChange={handleChange} sx={{ maxWidth: "65vw" }}>
{Object.values(division.offices).map(
(office: OfficeSpace | 0) => office !== 0 && <Tab key={office.loc} label={office.loc} value={office.loc} />,
)}
+1 -1
View File
@@ -38,7 +38,7 @@ export function CorporationRoot(): React.ReactElement {
return (
<Context.Corporation.Provider value={corporation}>
<Tabs variant="scrollable" value={divisionName} onChange={handleChange} sx={{ maxWidth: "65%" }} scrollButtons>
<Tabs variant="scrollable" value={divisionName} onChange={handleChange} sx={{ maxWidth: "65vw" }} scrollButtons>
<Tab label={corporation.name} value={"Overview"} />
{corporation.divisions.map((div) => (
<Tab key={div.name} label={div.name} value={div.name} />
+67
View File
@@ -0,0 +1,67 @@
import { ActiveFragment } from "./ActiveFragment";
import { IStaneksGift } from "./IStaneksGift";
export class DummyGift implements IStaneksGift {
storedCycles = 0;
fragments: ActiveFragment[] = [];
_width: number;
_height: number;
constructor(width: number, height: number, fragments: ActiveFragment[]) {
this.fragments = fragments;
this._width = width;
this._height = height;
}
width(): number {
return this._width;
}
height(): number {
return this._height;
}
charge(): any {
throw new Error("unimplemented for dummy gift");
}
process(): any {
throw new Error("unimplemented for dummy gift");
}
effect(): any {
throw new Error("unimplemented for dummy gift");
}
canPlace(): any {
throw new Error("unimplemented for dummy gift");
}
place(): any {
throw new Error("unimplemented for dummy gift");
}
findFragment(): any {
throw new Error("unimplemented for dummy gift");
}
fragmentAt(worldX: number, worldY: number): ActiveFragment | undefined {
for (const aFrag of this.fragments) {
if (aFrag.fullAt(worldX, worldY)) {
return aFrag;
}
}
return undefined;
}
delete(): any {
throw new Error("unimplemented for dummy gift");
}
clear(): any {
throw new Error("unimplemented for dummy gift");
}
count(): any {
throw new Error("unimplemented for dummy gift");
}
inBonus(): any {
throw new Error("unimplemented for dummy gift");
}
prestigeAugmentation(): any {
throw new Error("unimplemented for dummy gift");
}
prestigeSourceFile(): any {
throw new Error("unimplemented for dummy gift");
}
}
+23
View File
@@ -12,3 +12,26 @@ export function loadStaneksGift(saveString: string): void {
staneksGift = new StaneksGift();
}
}
export function zeros(dimensions: number[]): any {
const array = [];
for (let i = 0; i < dimensions[0]; ++i) {
array.push(dimensions.length == 1 ? 0 : zeros(dimensions.slice(1)));
}
return array;
}
export function calculateGrid(gift: IStaneksGift): number[][] {
const newgrid = zeros([gift.width(), gift.height()]) as unknown as number[][];
for (let i = 0; i < gift.width(); i++) {
for (let j = 0; j < gift.height(); j++) {
const fragment = gift.fragmentAt(i, j);
if (!fragment) continue;
newgrid[i][j] = 1;
}
}
return newgrid;
}
+33
View File
@@ -0,0 +1,33 @@
import { Box, Table } from "@mui/material";
import * as React from "react";
import { ActiveFragment } from "../ActiveFragment";
import { DummyGift } from "../DummyGift";
import { Grid } from "./Grid";
import { calculateGrid, zeros } from "../Helper";
interface IProps {
width: number;
height: number;
fragments: ActiveFragment[];
}
export function DummyGrid(props: IProps): React.ReactElement {
const gift = new DummyGift(props.width, props.height, props.fragments);
const activeGrid = calculateGrid(gift);
const ghostGrid = zeros([props.width, props.height]);
return (
<Box>
<Table>
<Grid
width={props.width}
height={props.height}
activeGrid={activeGrid}
ghostGrid={ghostGrid}
gift={gift}
enter={() => undefined}
click={() => undefined}
/>
</Table>
</Box>
);
}
+60
View File
@@ -0,0 +1,60 @@
import { TableBody, TableRow } from "@mui/material";
import * as React from "react";
import { ActiveFragment } from "../ActiveFragment";
import { IStaneksGift } from "../IStaneksGift";
import { Cell } from "./Cell";
interface IProps {
width: number;
height: number;
activeGrid: number[][];
ghostGrid: number[][];
gift: IStaneksGift;
enter(i: number, j: number): void;
click(i: number, j: number): void;
}
function randomColor(fragment: ActiveFragment): string {
// Can't set Math.random seed so copy casino. TODO refactor both RNG later.
let s1 = Math.pow((fragment.x + 1) * (fragment.y + 1), 10);
let s2 = s1;
let s3 = s1;
const colors = [];
for (let i = 0; i < 3; i++) {
s1 = (171 * s1) % 30269;
s2 = (172 * s2) % 30307;
s3 = (170 * s3) % 30323;
colors.push((s1 / 30269.0 + s2 / 30307.0 + s3 / 30323.0) % 1.0);
}
return `rgb(${colors[0] * 256}, ${colors[1] * 256}, ${colors[2] * 256})`;
}
export function Grid(props: IProps): React.ReactElement {
function color(worldX: number, worldY: number): string {
if (props.ghostGrid[worldX][worldY] && props.activeGrid[worldX][worldY]) return "red";
if (props.ghostGrid[worldX][worldY]) return "white";
if (props.activeGrid[worldX][worldY]) {
const fragment = props.gift.fragmentAt(worldX, worldY);
if (!fragment) throw new Error("ActiveFragment should not be null");
return randomColor(fragment);
}
return "";
}
// switch the width/length to make axis consistent.
const elems = [];
for (let j = 0; j < props.height; j++) {
const cells = [];
for (let i = 0; i < props.width; i++) {
cells.push(
<Cell key={i} onMouseEnter={() => props.enter(i, j)} onClick={() => props.click(i, j)} color={color(i, j)} />,
);
}
elems.push(<TableRow key={j}>{cells}</TableRow>);
}
return <TableBody>{elems}</TableBody>;
}
+11 -78
View File
@@ -1,62 +1,20 @@
import * as React from "react";
import { Fragment, NoneFragment } from "../Fragment";
import { ActiveFragment } from "../ActiveFragment";
import { FragmentType } from "../FragmentType";
import { IStaneksGift } from "../IStaneksGift";
import { Cell } from "./Cell";
import { FragmentInspector } from "./FragmentInspector";
import { FragmentSelector } from "./FragmentSelector";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import TableRow from "@mui/material/TableRow";
import TableBody from "@mui/material/TableBody";
import { Table } from "../../ui/React/Table";
function zeros(dimensions: number[]): any {
const array = [];
for (let i = 0; i < dimensions[0]; ++i) {
array.push(dimensions.length == 1 ? 0 : zeros(dimensions.slice(1)));
}
return array;
}
function randomColor(fragment: ActiveFragment): string {
// Can't set Math.random seed so copy casino. TODO refactor both RNG later.
let s1 = Math.pow((fragment.x + 1) * (fragment.y + 1), 10);
let s2 = s1;
let s3 = s1;
const colors = [];
for (let i = 0; i < 3; i++) {
s1 = (171 * s1) % 30269;
s2 = (172 * s2) % 30307;
s3 = (170 * s3) % 30323;
colors.push((s1 / 30269.0 + s2 / 30307.0 + s3 / 30323.0) % 1.0);
}
return `rgb(${colors[0] * 256}, ${colors[1] * 256}, ${colors[2] * 256})`;
}
import { Grid } from "./Grid";
import { zeros, calculateGrid } from "../Helper";
interface IProps {
gift: IStaneksGift;
}
export function MainBoard(props: IProps): React.ReactElement {
function calculateGrid(gift: IStaneksGift): any {
const newgrid = zeros([gift.width(), gift.height()]);
for (let i = 0; i < gift.width(); i++) {
for (let j = 0; j < gift.height(); j++) {
const fragment = gift.fragmentAt(i, j);
if (!fragment) continue;
newgrid[i][j] = 1;
}
}
return newgrid;
}
const [grid, setGrid] = React.useState(calculateGrid(props.gift));
const [ghostGrid, setGhostGrid] = React.useState(zeros([props.gift.width(), props.gift.height()]));
const [pos, setPos] = React.useState([0, 0]);
@@ -96,44 +54,11 @@ export function MainBoard(props: IProps): React.ReactElement {
setGrid(calculateGrid(props.gift));
}
function color(worldX: number, worldY: number): string {
if (ghostGrid[worldX][worldY] && grid[worldX][worldY]) return "red";
if (ghostGrid[worldX][worldY]) return "white";
if (grid[worldX][worldY]) {
const fragment = props.gift.fragmentAt(worldX, worldY);
if (!fragment) throw new Error("ActiveFragment should not be null");
return randomColor(fragment);
}
return "";
}
function clear(): void {
props.gift.clear();
setGrid(zeros([props.gift.width(), props.gift.height()]));
}
// switch the width/length to make axis consistent.
const elems = [];
for (let j = 0; j < props.gift.height(); j++) {
const cells = [];
for (let i = 0; i < props.gift.width(); i++) {
cells.push(
<Cell
key={i}
onMouseEnter={() => moveGhost(i, j, rotation)}
onClick={() => clickAt(i, j)}
color={color(i, j)}
/>,
);
}
elems.push(
<TableRow key={j} className="staneksgift_row">
{cells}
</TableRow>,
);
}
function updateSelectedFragment(fragment: Fragment): void {
setSelectedFragment(fragment);
const newgrid = zeros([props.gift.width(), props.gift.height()]);
@@ -162,7 +87,15 @@ export function MainBoard(props: IProps): React.ReactElement {
<Button onClick={clear}>Clear</Button>
<Box display="flex">
<Table>
<TableBody>{elems}</TableBody>
<Grid
width={props.gift.width()}
height={props.gift.height()}
activeGrid={grid}
ghostGrid={ghostGrid}
gift={props.gift}
enter={(i, j) => moveGhost(i, j, rotation)}
click={(i, j) => clickAt(i, j)}
/>
</Table>
<FragmentInspector gift={props.gift} x={pos[0]} y={pos[1]} fragment={props.gift.fragmentAt(pos[0], pos[1])} />
</Box>
+161 -1
View File
@@ -4,7 +4,12 @@ import { CONSTANTS } from "../../Constants";
import { StaneksGiftEvents } from "../StaneksGiftEvents";
import { MainBoard } from "./MainBoard";
import { IStaneksGift } from "../IStaneksGift";
import { Info } from "@mui/icons-material";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import Typography from "@mui/material/Typography";
import { ActiveFragment } from "../ActiveFragment";
import { Fragments } from "../Fragment";
import { DummyGrid } from "./DummyGrid";
type IProps = {
staneksGift: IStaneksGift;
@@ -18,7 +23,162 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
useEffect(() => StaneksGiftEvents.subscribe(rerender), []);
return (
<>
<Typography variant="h4">Stanek's Gift</Typography>
<Typography variant="h4">
Stanek's Gift
<Info
sx={{ ml: 1, mb: 0 }}
color="info"
onClick={() =>
dialogBoxCreate(
<>
<Typography>
Stanek's Gift is a powerful, unique augmentation obtained by joining the Church of the Machine God,
which can be found in Chongqing. It is only possible to join the church if you are in BitNode 13 or
possess at least one level of Source File 13, and you will be turned away if you have purchased or
installed any augmentations beside NeuroFlux Governors. You are, however, permitted to install
augmentations as normal once you have joined the church.
</Typography>
<br />
<Typography>
Initially, those bearing the gift will find that its overwhelming power worsens all of their stats by
10%. This penalty can be overcome in time by receiving free upgrades from the Church of the Machine
God faction, but the reputation requirements for these upgrades are steep, and you can only obtain
reputation with the church by charging the gift.
</Typography>
<br />
<Typography>
In order to charge Stanek's Gift, the user must first arrange stat-modifying fragments within the grid
located on the device. This may be done manually or by scripts that utilize the Stanek's Gift
Netscript API. Not every tile of the grid must house a fragment, but fragments cannot overlap or
otherwise share tiles. Fragments can be rotated, but their design does not allow them to be flipped
around to mirror their original shape. Note that the size of the grid is determined by the BitNode you
are currently in and the level of your Source File 13, if applicable.
</Typography>
<br />
<Typography>
There exist two kinds of fragments. The first are Stat Fragments, which take up 4 tiles of the grid.
Each Stat Fragment is unique, and there is only one of each. There is no way to obtain more of these
fragments. Each Stat Fragment has an associated stat that it will improve, as well as a multiplier on
its effectiveness known as power. When initially placed, a Stat Fragment will have no effect. In order
for the fragment to gain stat boosts, it must be charged. The other kind of fragments are known as
Booster Fragments, which take up 5 tiles of the grid. There is no shortage of Booster Fragments, and
it is virtually impossible to run out of them. While not providing any direct stat increases to their
user, Stat Fragments increase the efficacy of adjacent Stat Fragments by 10%, and do not need to be
charged.
</Typography>
<DummyGrid
width={4}
height={4}
fragments={[
new ActiveFragment({
x: 0,
y: 0,
rotation: 0,
fragment: Fragments.find((f) => f.id === 5) ?? Fragments[0],
}),
new ActiveFragment({
x: 0,
y: 2,
rotation: 0,
fragment: Fragments.find((f) => f.id === 101) ?? Fragments[0],
}),
]}
/>
<Typography sx={{ fontStyle: "italic" }}>
This boost provides a bonus to the touching fragment
</Typography>
<DummyGrid
width={4}
height={4}
fragments={[
new ActiveFragment({
x: 0,
y: 1,
rotation: 3,
fragment: Fragments.find((f) => f.id === 100) ?? Fragments[0],
}),
new ActiveFragment({
x: 0,
y: 0,
rotation: 2,
fragment: Fragments.find((f) => f.id === 1) ?? Fragments[0],
}),
]}
/>
<Typography sx={{ fontStyle: "italic" }}>
Even though the booster touches many tiles, the bonus is only applied once.
</Typography>
<DummyGrid
width={4}
height={4}
fragments={[
new ActiveFragment({
x: 0,
y: 0,
rotation: 0,
fragment: Fragments.find((f) => f.id === 5) ?? Fragments[0],
}),
new ActiveFragment({
x: 2,
y: 0,
rotation: 0,
fragment: Fragments.find((f) => f.id === 105) ?? Fragments[0],
}),
]}
/>
<Typography sx={{ fontStyle: "italic" }}>
Even though the booster touches many tiles, the bonus is only applied once.
</Typography>
<DummyGrid
width={4}
height={4}
fragments={[
new ActiveFragment({
x: 0,
y: 0,
rotation: 1,
fragment: Fragments.find((f) => f.id === 27) ?? Fragments[0],
}),
new ActiveFragment({
x: 0,
y: 1,
rotation: 2,
fragment: Fragments.find((f) => f.id === 100) ?? Fragments[0],
}),
new ActiveFragment({
x: 2,
y: 0,
rotation: 1,
fragment: Fragments.find((f) => f.id === 30) ?? Fragments[0],
}),
]}
/>
<Typography sx={{ fontStyle: "italic" }}>
This booster provides bonus to all fragment it touches.
</Typography>
<br />
<Typography>
Stat Fragments are charged using the stanek.chargeFragment(rootX, rootY) NetScript API function. The
charging process ordinarily takes 1000ms to complete, but only takes 200ms during bonus time. When the
function finishes executing, the fragment's charge levels will be raised by an amount corresponding to
the number of threads that were used. Note that it is no more effective to charge a fragment many
times with few threads than to charge few times with many threads, so there is no need to distribute
charging jobs across multiple scripts. As a Stat Fragment's charge level is increased, its bonuses
will increase, but there will be diminishing returns. As such, it is generally most efficient to
charge all of the placed fragments equally. The charge level of a fragment will not decrease over
time, but it will be reset to 0 upon removing it from the board or installing augmentations.
</Typography>
</>,
)
}
/>
</Typography>
<Typography>
The gift is a grid on which you can place upgrades called fragments. The main type of fragment increases a stat,
like your hacking skill or agility exp. Once a stat fragment is placed it then needs to be charged via scripts
+5 -10
View File
@@ -72,17 +72,12 @@ export function buyDarkwebItem(itemName: string): void {
// buy and push
Player.loseMoney(item.price, "other");
const programsRef = Player.getHomeComputer().programs;
// Remove partially created program if there is one
const existingPartialExeIndex = programsRef.findIndex(
(program) => item?.program && program.startsWith(item?.program),
);
// findIndex returns -1 if there is no match, we only want to splice on a match
if (existingPartialExeIndex > -1) {
programsRef.splice(existingPartialExeIndex, 1);
Player.getHomeComputer().pushProgram(item.program);
// Cancel if the program is in progress of writing
if (Player.createProgramName === item.program) {
Player.isWorking = false;
Player.resetWorkStatus();
}
// Add the newly bought, full .exe
Player.getHomeComputer().programs.push(item.program);
Terminal.print(
"You have purchased the " + item.program + " program. The new program can be found on your home computer.",
+48 -53
View File
@@ -1,19 +1,18 @@
import { Clear, ExpandMore, Reply, ReplyAll } from "@mui/icons-material";
import {
Accordion,
AccordionDetails,
AccordionSummary,
Button,
IconButton,
MenuItem,
Select,
SelectChangeEvent,
Typography,
} from "@mui/material";
import React, { useState } from "react";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import Typography from "@mui/material/Typography";
import MenuItem from "@mui/material/MenuItem";
import IconButton from "@mui/material/IconButton";
import ReplyAllIcon from "@mui/icons-material/ReplyAll";
import ReplyIcon from "@mui/icons-material/Reply";
import ClearIcon from "@mui/icons-material/Clear";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
player: IPlayer;
@@ -39,50 +38,46 @@ export function Augmentations(props: IProps): React.ReactElement {
props.player.augmentations = [];
}
function clearQueuedAugs(): void {
props.player.queuedAugmentations = [];
}
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<AccordionSummary expandIcon={<ExpandMore />}>
<Typography>Augmentations</Typography>
</AccordionSummary>
<AccordionDetails>
<table>
<tbody>
<tr>
<td>
<Typography>Aug:</Typography>
</td>
<td>
<Select
onChange={setAugmentationDropdown}
value={augmentation}
startAdornment={
<>
<IconButton onClick={queueAllAugs} size="large">
<ReplyAllIcon />
</IconButton>
<IconButton onClick={queueAug} size="large">
<ReplyIcon />
</IconButton>
</>
}
endAdornment={
<>
<IconButton onClick={clearAugs} size="large">
<ClearIcon />
</IconButton>
</>
}
>
{Object.values(AugmentationNames).map((aug) => (
<MenuItem key={aug} value={aug}>
{aug}
</MenuItem>
))}
</Select>
</td>
</tr>
</tbody>
</table>
<Select
onChange={setAugmentationDropdown}
value={augmentation}
startAdornment={
<>
<IconButton onClick={queueAllAugs} size="large">
<ReplyAll />
</IconButton>
<IconButton onClick={queueAug} size="large">
<Reply />
</IconButton>
</>
}
endAdornment={
<>
<IconButton onClick={clearAugs} size="large">
<Clear />
</IconButton>
</>
}
>
{Object.values(AugmentationNames).map((aug) => (
<MenuItem key={aug} value={aug}>
{aug}
</MenuItem>
))}
</Select>
<Button sx={{ display: "block" }} onClick={clearQueuedAugs}>
Clear Queued Augmentations
</Button>
</AccordionDetails>
</Accordion>
);
+6 -7
View File
@@ -2,7 +2,7 @@ import { Player } from "./Player";
import { Router } from "./ui/GameRoot";
import { removeLeadingSlash } from "./Terminal/DirectoryHelpers";
import { Terminal } from "./Terminal";
import { SnackbarEvents } from "./ui/React/Snackbar";
import { SnackbarEvents, ToastVariant } from "./ui/React/Snackbar";
import { IMap, IReturnStatus } from "./types";
import { GetServer } from "./Server/AllServers";
import { ImportPlayerData, SaveData, saveObject } from "./SaveObject";
@@ -111,8 +111,7 @@ function initAppNotifier(): void {
if (!fn) fn = Terminal.print;
fn.bind(Terminal)(message);
},
toast: (message: string, type: "info" | "success" | "warning" | "error", duration = 2000) =>
SnackbarEvents.emit(message, type, duration),
toast: (message: string, type: ToastVariant, duration = 2000) => SnackbarEvents.emit(message, type, duration),
};
// Will be consumud by the electron wrapper.
@@ -127,7 +126,7 @@ function initSaveFunctions(): void {
saveObject.exportGame();
} catch (error) {
console.log(error);
SnackbarEvents.emit("Could not export game.", "error", 2000);
SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000);
}
},
triggerScriptsExport: (): void => exportScripts("*", Player.getHomeComputer()),
@@ -176,7 +175,7 @@ function initElectronBridge(): void {
})
.catch((error: any) => {
console.log(error);
SnackbarEvents.emit("Could not save game.", "error", 2000);
SnackbarEvents.emit("Could not save game.", ToastVariant.ERROR, 2000);
});
});
bridge.receive("trigger-game-export", () => {
@@ -184,7 +183,7 @@ function initElectronBridge(): void {
(window as any).appSaveFns.triggerGameExport();
} catch (error) {
console.log(error);
SnackbarEvents.emit("Could not export game.", "error", 2000);
SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000);
}
});
bridge.receive("trigger-scripts-export", () => {
@@ -192,7 +191,7 @@ function initElectronBridge(): void {
(window as any).appSaveFns.triggerScriptsExport();
} catch (error) {
console.log(error);
SnackbarEvents.emit("Could not export scripts.", "error", 2000);
SnackbarEvents.emit("Could not export scripts.", ToastVariant.ERROR, 2000);
}
});
}
+4 -5
View File
@@ -81,7 +81,6 @@ export function hasAugmentationPrereqs(aug: Augmentation): boolean {
}
export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string {
const factionInfo = fac.getInfo();
const hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) {
const txt = `You must first purchase or install ${aug.prereqs.join(",")} before you can purchase this one.`;
@@ -90,7 +89,7 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
} else {
dialogBoxCreate(txt);
}
} else if (aug.baseCost !== 0 && Player.money < aug.baseCost * factionInfo.augmentationPriceMult) {
} else if (aug.baseCost !== 0 && Player.money < aug.baseCost) {
const txt = "You don't have enough money to purchase " + aug.name;
if (sing) {
return txt;
@@ -102,14 +101,14 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
return txt;
}
dialogBoxCreate(txt);
} else if (aug.baseCost === 0 || Player.money >= aug.baseCost * factionInfo.augmentationPriceMult) {
} else if (aug.baseCost === 0 || Player.money >= aug.baseCost) {
const queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeurofluxLevel();
}
Player.queuedAugmentations.push(queuedAugmentation);
Player.loseMoney(aug.baseCost * factionInfo.augmentationPriceMult, "augmentations");
Player.loseMoney(aug.baseCost, "augmentations");
// If you just purchased Neuroflux Governor, recalculate the cost
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
@@ -203,7 +202,7 @@ export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Factio
// Remove special augs
augs = augs.filter((a) => !a.isSpecial);
const blacklist: string[] = [AugmentationNames.NeuroFluxGovernor];
const blacklist: string[] = [AugmentationNames.NeuroFluxGovernor, AugmentationNames.CongruityImplant];
if (player.bitNodeN !== 2) {
// TRP is not available outside of BN2 for Gangs
+233 -361
View File
@@ -2,20 +2,20 @@ import React from "react";
import { IMap } from "../types";
import { FactionNames } from "./data/FactionNames";
interface FactionInfoParams {
infoText?: JSX.Element;
enemies?: string[];
offerHackingWork?: boolean;
offerFieldWork?: boolean;
offerSecurityWork?: boolean;
special?: boolean;
keepOnInstall?: boolean;
}
/**
* Contains the "information" property for all the Factions, which is just a description of each faction
*/
export class FactionInfo {
/**
* The multiplier to apply to augmentation base purchase price.
*/
augmentationPriceMult: number;
/**
* The multiplier to apply to augmentation reputation base requirement.
*/
augmentationRepRequirementMult: number;
/**
* The names of all other factions considered to be enemies to this faction.
*/
@@ -31,11 +31,6 @@ export class FactionInfo {
*/
offerFieldWork: boolean;
/**
* A flag indicating if the faction supports hacking missions to earn reputation.
*/
offerHackingMission: boolean;
/**
* A flag indicating if the faction supports hacking work to earn reputation.
*/
@@ -56,32 +51,19 @@ export class FactionInfo {
*/
special: boolean;
constructor(
infoText: JSX.Element,
enemies: string[],
offerHackingMission: boolean,
offerHackingWork: boolean,
offerFieldWork: boolean,
offerSecurityWork: boolean,
special: boolean,
keep: boolean,
) {
this.infoText = infoText;
this.enemies = enemies;
this.offerHackingMission = offerHackingMission;
this.offerHackingWork = offerHackingWork;
this.offerFieldWork = offerFieldWork;
this.offerSecurityWork = offerSecurityWork;
constructor(params: FactionInfoParams) {
this.infoText = params.infoText ?? <></>;
this.enemies = params.enemies ?? [];
this.offerHackingWork = params.offerHackingWork ?? false;
this.offerFieldWork = params.offerFieldWork ?? false;
this.offerSecurityWork = params.offerSecurityWork ?? false;
// These are always all 1 for now.
this.augmentationPriceMult = 1;
this.augmentationRepRequirementMult = 1;
this.keep = keep;
this.special = special;
this.keep = params.keepOnInstall ?? false;
this.special = params.special ?? false;
}
offersWork(): boolean {
return this.offerFieldWork || this.offerHackingMission || this.offerHackingWork || this.offerSecurityWork;
return this.offerFieldWork || this.offerHackingWork || this.offerSecurityWork;
}
}
@@ -91,35 +73,25 @@ export class FactionInfo {
// tslint:disable-next-line:variable-name
export const FactionInfos: IMap<FactionInfo> = {
// Endgame
[FactionNames.Illuminati]: new FactionInfo(
(
[FactionNames.Illuminati]: new FactionInfo({
infoText: (
<>
Humanity never changes. No matter how civilized society becomes, it will eventually fall back into chaos. And
from this chaos, we are the invisible hand that guides them to order.{" "}
</>
),
[],
true,
true,
true,
false,
false,
false,
),
offerHackingWork: true,
offerFieldWork: true,
}),
[FactionNames.Daedalus]: new FactionInfo(
<>Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.</>,
[],
true,
true,
true,
false,
false,
false,
),
[FactionNames.Daedalus]: new FactionInfo({
infoText: <>Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.</>,
offerHackingWork: true,
offerFieldWork: true,
}),
[FactionNames.TheCovenant]: new FactionInfo(
(
[FactionNames.TheCovenant]: new FactionInfo({
infoText: (
<>
Surrender yourself. Give up your empty individuality to become part of something great, something eternal.
Become a slave. Submit your mind, body, and soul. Only then can you set yourself free.
@@ -128,35 +100,27 @@ export const FactionInfos: IMap<FactionInfo> = {
Only then can you discover immortality.
</>
),
[],
true,
true,
true,
false,
false,
false,
),
offerHackingWork: true,
offerFieldWork: true,
}),
// Megacorporations, each forms its own faction
[FactionNames.ECorp]: new FactionInfo(
(
[FactionNames.ECorp]: new FactionInfo({
infoText: (
<>
{FactionNames.ECorp}'s mission is simple: to connect the world of today with the technology of tomorrow. With
our wide range of Internet-related software and commercial hardware, {FactionNames.ECorp} makes the world's
information universally accessible.
</>
),
[],
true,
true,
true,
true,
false,
true,
),
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.MegaCorp]: new FactionInfo(
(
[FactionNames.MegaCorp]: new FactionInfo({
infoText: (
<>
{FactionNames.MegaCorp} does what no other dares to do. We imagine. We create. We invent. We create what others
have never even dreamed of. Our work fills the world's needs for food, water, power, and transportation on an
@@ -167,17 +131,14 @@ export const FactionInfos: IMap<FactionInfo> = {
the world.
</>
),
[],
true,
true,
true,
true,
false,
true,
),
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.BachmanAssociates]: new FactionInfo(
(
[FactionNames.BachmanAssociates]: new FactionInfo({
infoText: (
<>
Where Law and Business meet - thats where we are.
<br />
@@ -185,112 +146,87 @@ export const FactionInfos: IMap<FactionInfo> = {
Legal Insight - Business Instinct - Innovative Experience.
</>
),
[],
true,
true,
true,
true,
false,
true,
),
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.BladeIndustries]: new FactionInfo(
<>Augmentation is Salvation.</>,
[],
true,
true,
true,
true,
false,
true,
),
[FactionNames.BladeIndustries]: new FactionInfo({
infoText: <>Augmentation is Salvation.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.NWO]: new FactionInfo(
(
[FactionNames.NWO]: new FactionInfo({
infoText: (
<>
Humans don't truly desire freedom. They want to be observed, understood, and judged. They want to be given
purpose and direction in life. That is why they created God. And that is why they created civilization - not
because of willingness, but because of a need to be incorporated into higher orders of structure and meaning.
</>
),
[],
true,
true,
true,
true,
false,
true,
),
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.ClarkeIncorporated]: new FactionInfo(
<>The Power of the Genome - Unlocked.</>,
[],
true,
true,
true,
true,
false,
true,
),
[FactionNames.ClarkeIncorporated]: new FactionInfo({
infoText: <>The Power of the Genome - Unlocked.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.OmniTekIncorporated]: new FactionInfo(
<>Simply put, our mission is to design and build robots that make a difference.</>,
[],
true,
true,
true,
true,
false,
true,
),
[FactionNames.OmniTekIncorporated]: new FactionInfo({
infoText: <>Simply put, our mission is to design and build robots that make a difference.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.FourSigma]: new FactionInfo(
(
[FactionNames.FourSigma]: new FactionInfo({
infoText: (
<>
The scientific method is the best way to approach investing. Big strategies backed up with big data. Driven by
deep learning and innovative ideas. And improved by iteration. That's {FactionNames.FourSigma}.
</>
),
[],
true,
true,
true,
true,
false,
true,
),
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
[FactionNames.KuaiGongInternational]: new FactionInfo(
<>Dream big. Work hard. Make history.</>,
[],
true,
true,
true,
true,
false,
true,
),
[FactionNames.KuaiGongInternational]: new FactionInfo({
infoText: <>Dream big. Work hard. Make history.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
// Other Corporations
[FactionNames.FulcrumSecretTechnologies]: new FactionInfo(
(
[FactionNames.FulcrumSecretTechnologies]: new FactionInfo({
infoText: (
<>
The human organism has an innate desire to worship. That is why they created gods. If there were no gods, it
would be necessary to create them. And now we can.
</>
),
[],
true,
true,
false,
true,
false,
true,
),
offerHackingWork: true,
offerSecurityWork: true,
keepOnInstall: true,
}),
// Hacker groups
[FactionNames.BitRunners]: new FactionInfo(
(
[FactionNames.BitRunners]: new FactionInfo({
infoText: (
<>
Our entire lives are controlled by bits. All of our actions, our thoughts, our personal information. It's all
transformed into bits, stored in bits, communicated through bits. Its impossible for any person to move, to
@@ -302,17 +238,11 @@ export const FactionInfos: IMap<FactionInfo> = {
Those who run the bits, run the world.
</>
),
[],
true,
true,
false,
false,
false,
false,
),
offerHackingWork: true,
}),
[FactionNames.TheBlackHand]: new FactionInfo(
(
[FactionNames.TheBlackHand]: new FactionInfo({
infoText: (
<>
The world, so afraid of strong government, now has no government. Only power - Digital power. Financial power.
Technological power. And those at the top rule with an invisible hand. They built a society where the rich get
@@ -322,17 +252,13 @@ export const FactionInfos: IMap<FactionInfo> = {
So much pain. So many lives. Their darkness must end.
</>
),
[],
true,
true,
true,
false,
false,
false,
),
offerHackingWork: true,
offerFieldWork: true,
}),
// prettier-ignore
[FactionNames.NiteSec]: new FactionInfo(<>
[FactionNames.NiteSec]: new FactionInfo({
infoText:(<>
{" __..__ "}<br />
{" _.nITESECNIt. "}<br />
{" .-'NITESECNITESEc. "}<br />
@@ -367,105 +293,87 @@ export const FactionInfos: IMap<FactionInfo> = {
{" / .d/$/$; , ; "}<br />
{" d .dNITESEC $ | "}<br />
{" :bp.__.gNITESEC/$ :$ ; "}<br />
{" NITESECNITESECNIT /$b : "}<br /></>,
[],
true,
true,
false,
false,
false,
false,
),
{" NITESECNITESECNIT /$b : "}<br /></>),
offerHackingWork: true,
offerFieldWork: false,
offerSecurityWork: false,
special: false,
keepOnInstall: false,
}),
// City factions, essentially governments
[FactionNames.Aevum]: new FactionInfo(
<>The Silicon City.</>,
[FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven],
true,
true,
true,
true,
false,
false,
),
[FactionNames.Chongqing]: new FactionInfo(
<>Serve the People.</>,
[FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
true,
true,
true,
true,
false,
false,
),
[FactionNames.Ishima]: new FactionInfo(
<>The East Asian Order of the Future.</>,
[FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
true,
true,
true,
true,
false,
false,
),
[FactionNames.NewTokyo]: new FactionInfo(
<>Asia's World City.</>,
[FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
true,
true,
true,
true,
false,
false,
),
[FactionNames.Sector12]: new FactionInfo(
<>The City of the Future.</>,
[FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven],
true,
true,
true,
true,
false,
false,
),
[FactionNames.Volhaven]: new FactionInfo(
<>Benefit, Honor, and Glory.</>,
[FactionNames.Chongqing, FactionNames.Sector12, FactionNames.NewTokyo, FactionNames.Aevum, FactionNames.Ishima],
true,
true,
true,
true,
false,
false,
),
[FactionNames.Aevum]: new FactionInfo({
infoText: <>The Silicon City.</>,
enemies: [FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.Chongqing]: new FactionInfo({
infoText: <>Serve the People.</>,
enemies: [FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.Ishima]: new FactionInfo({
infoText: <>The East Asian Order of the Future.</>,
enemies: [FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.NewTokyo]: new FactionInfo({
infoText: <>Asia's World City.</>,
enemies: [FactionNames.Sector12, FactionNames.Aevum, FactionNames.Volhaven],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.Sector12]: new FactionInfo({
infoText: <>The City of the Future.</>,
enemies: [FactionNames.Chongqing, FactionNames.NewTokyo, FactionNames.Ishima, FactionNames.Volhaven],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.Volhaven]: new FactionInfo({
infoText: <>Benefit, Honor, and Glory.</>,
enemies: [
FactionNames.Chongqing,
FactionNames.Sector12,
FactionNames.NewTokyo,
FactionNames.Aevum,
FactionNames.Ishima,
],
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
// Criminal Organizations/Gangs
[FactionNames.SpeakersForTheDead]: new FactionInfo(
<>It is better to reign in Hell than to serve in Heaven.</>,
[],
true,
true,
true,
true,
false,
false,
),
[FactionNames.SpeakersForTheDead]: new FactionInfo({
infoText: <>It is better to reign in Hell than to serve in Heaven.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.TheDarkArmy]: new FactionInfo(
<>The World doesn't care about right or wrong. It only cares about power.</>,
[],
true,
true,
true,
false,
false,
false,
),
[FactionNames.TheDarkArmy]: new FactionInfo({
infoText: <>The World doesn't care about right or wrong. It only cares about power.</>,
offerHackingWork: true,
offerFieldWork: true,
}),
[FactionNames.TheSyndicate]: new FactionInfo(<>Honor holds you back.</>, [], true, true, true, true, false, false),
[FactionNames.TheSyndicate]: new FactionInfo({
infoText: <>Honor holds you back.</>,
offerHackingWork: true,
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.Silhouette]: new FactionInfo(
(
[FactionNames.Silhouette]: new FactionInfo({
infoText: (
<>
Corporations have filled the void of power left behind by the collapse of Western government. The issue is
they've become so big that you don't know who they're working for. And if you're employed at one of these
@@ -475,100 +383,66 @@ export const FactionInfos: IMap<FactionInfo> = {
That's terror. Terror, fear, and corruption. All born into the system, all propagated by the system.
</>
),
[],
true,
true,
true,
false,
false,
false,
),
offerHackingWork: true,
offerFieldWork: true,
}),
[FactionNames.Tetrads]: new FactionInfo(
<>Following the mandate of Heaven and carrying out the way.</>,
[],
false,
false,
true,
true,
false,
false,
),
[FactionNames.Tetrads]: new FactionInfo({
infoText: <>Following the mandate of Heaven and carrying out the way.</>,
[FactionNames.SlumSnakes]: new FactionInfo(
<>{FactionNames.SlumSnakes} rule!</>,
[],
false,
false,
true,
true,
false,
false,
),
offerFieldWork: true,
offerSecurityWork: true,
}),
[FactionNames.SlumSnakes]: new FactionInfo({
infoText: <>{FactionNames.SlumSnakes} rule!</>,
offerFieldWork: true,
offerSecurityWork: true,
}),
// Earlygame factions - factions the player will prestige with early on that don't belong in other categories.
[FactionNames.Netburners]: new FactionInfo(
<>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}</>,
[],
true,
true,
false,
false,
false,
false,
),
[FactionNames.Netburners]: new FactionInfo({
infoText: <>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}</>,
offerHackingWork: true,
}),
[FactionNames.TianDiHui]: new FactionInfo(
<>Obey Heaven and work righteously.</>,
[],
true,
true,
false,
true,
false,
false,
),
[FactionNames.TianDiHui]: new FactionInfo({
infoText: <>Obey Heaven and work righteously.</>,
offerHackingWork: true,
[FactionNames.CyberSec]: new FactionInfo(
(
offerSecurityWork: true,
}),
[FactionNames.CyberSec]: new FactionInfo({
infoText: (
<>
The Internet is the first thing that was built that we don't fully understand, the largest experiment in anarchy
that we have ever had. And as the world becomes increasingly dominated by it, society approaches the brink of
total chaos. We serve only to protect society, to protect humanity, to protect the world from imminent collapse.
</>
),
[],
true,
true,
false,
false,
false,
false,
),
offerHackingWork: true,
}),
// Special Factions
[FactionNames.Bladeburners]: new FactionInfo(
(
[FactionNames.Bladeburners]: new FactionInfo({
infoText: (
<>
It's too bad they won't live. But then again, who does?
<br />
<br />
Note that for this faction, reputation can only be gained through {FactionNames.Bladeburners} actions.
Completing {FactionNames.Bladeburners}
contracts/operations will increase your reputation.
Note that for this faction, reputation can only be gained through {FactionNames.Bladeburners} actions.{" "}
Completing {FactionNames.Bladeburners} contracts/operations will increase your reputation.
</>
),
[],
false,
false,
false,
false,
true,
false,
),
special: true,
}),
// prettier-ignore
[FactionNames.ChurchOfTheMachineGod]: new FactionInfo(<>
[FactionNames.ChurchOfTheMachineGod]: new FactionInfo({
infoText:(<>
{" `` "}<br />
{" -odmmNmds: "}<br />
{" `hNmo:..-omNh. "}<br />
@@ -600,13 +474,11 @@ export const FactionInfos: IMap<FactionInfo> = {
Many cultures predict an end to humanity in the near future, a final
Armageddon that will end the world; but we disagree.
<br /><br />Note that for this faction, reputation can
only be gained by charging Stanek's gift.</>,
[],
false,
false,
false,
false,
true,
true,
),
only be gained by charging Stanek's gift.</>),
offerHackingWork: false,
offerFieldWork: false,
offerSecurityWork: false,
special: true,
keepOnInstall: true,
}),
};
+2 -2
View File
@@ -78,10 +78,10 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
const augs = getAugs();
function canBuy(augName: string): boolean {
const aug = Augmentations[augName];
const repCost = aug.baseRepRequirement * props.faction.getInfo().augmentationRepRequirementMult;
const repCost = aug.baseRepRequirement;
const hasReq = props.faction.playerReputation >= repCost;
const hasRep = hasAugmentationPrereqs(aug);
const hasCost = aug.baseCost !== 0 && player.money > aug.baseCost * props.faction.getInfo().augmentationPriceMult;
const hasCost = aug.baseCost !== 0 && player.money > aug.baseCost;
return hasCost && hasReq && hasRep;
}
const buy = augs.filter(canBuy).sort((augName1, augName2) => {
+1 -2
View File
@@ -20,7 +20,6 @@ interface IProps {
export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
const player = use.Player();
const factionInfo = props.faction.getInfo();
function buy(): void {
if (!isRepeatableAug(props.aug) && player.hasAugmentation(props.aug)) {
@@ -43,7 +42,7 @@ export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
<br />
<br />
Would you like to purchase the {props.aug.name} Augmentation for&nbsp;
<Money money={props.aug.baseCost * factionInfo.augmentationPriceMult} />?
<Money money={props.aug.baseCost} />?
<br />
<br />
</Typography>
+3 -3
View File
@@ -84,11 +84,11 @@ export function PurchaseableAugmentation(props: IProps): React.ReactElement {
return <></>;
}
const moneyCost = aug.baseCost * props.faction.getInfo().augmentationPriceMult;
const repCost = aug.baseRepRequirement * props.faction.getInfo().augmentationRepRequirementMult;
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 * props.faction.getInfo().augmentationPriceMult;
const hasCost = aug.baseCost === 0 || props.p.money > aug.baseCost;
// Determine UI properties
const color: "error" | "primary" = !hasReq || !hasRep || !hasCost ? "error" : "primary";
+5 -3
View File
@@ -225,7 +225,6 @@ export class Gang implements IGang {
if (AllGangs[otherGang].territory <= 0) return;
const territoryGain = calculateTerritoryGain(thisGang, otherGang);
AllGangs[thisGang].territory += territoryGain;
if (AllGangs[thisGang].territory > 0.999) AllGangs[thisGang].territory = 1;
AllGangs[otherGang].territory -= territoryGain;
if (thisGang === gangName) {
this.clash(true); // Player won
@@ -239,9 +238,7 @@ export class Gang implements IGang {
if (AllGangs[thisGang].territory <= 0) return;
const territoryGain = calculateTerritoryGain(otherGang, thisGang);
AllGangs[thisGang].territory -= territoryGain;
if (AllGangs[otherGang].territory < 0.001) AllGangs[otherGang].territory = 0;
AllGangs[otherGang].territory += territoryGain;
if (AllGangs[otherGang].territory > 0.999) AllGangs[otherGang].territory = 1;
if (thisGang === gangName) {
this.clash(false); // Player lost
} else if (otherGang === gangName) {
@@ -251,6 +248,11 @@ export class Gang implements IGang {
AllGangs[thisGang].power *= 1 / 1.01;
}
}
const total = Object.values(AllGangs)
.map((g) => g.territory)
.reduce((p, c) => p + c, 0);
Object.values(AllGangs).forEach((g) => (g.territory /= total));
}
}
}
+6 -1
View File
@@ -11,6 +11,7 @@ import { Money } from "../../ui/React/Money";
import { numeralWrapper } from "../../ui/numeralFormat";
import { MathJaxWrapper } from "../../MathJaxWrapper";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
type IProps = {
p: IPlayer;
@@ -30,9 +31,13 @@ export function RamButton(props: IProps): React.ReactElement {
props.rerender();
}
const bnMult = BitNodeMultipliers.HomeComputerRamCost === 1 ? "" : `\\cdot ${BitNodeMultipliers.HomeComputerRamCost}`;
console.log(BitNodeMultipliers.HomeComputerRamCost);
return (
<Tooltip
title={<MathJaxWrapper>{`\\(\\large{cost = 3.2 \\cdot 10^3 \\cdot 1.58^{log_2{(ram)}}}\\)`}</MathJaxWrapper>}
title={
<MathJaxWrapper>{`\\(\\large{cost = ram \\cdot 3.2 \\cdot 10^4 \\cdot 1.58^{log_2{(ram)}}} ${bnMult}\\)`}</MathJaxWrapper>
}
>
<span>
<br />
+13 -3
View File
@@ -24,7 +24,7 @@ import { joinFaction } from "../../Faction/FactionHelpers";
import { use } from "../../ui/Context";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { SnackbarEvents } from "../../ui/React/Snackbar";
import { SnackbarEvents, ToastVariant } from "../../ui/React/Snackbar";
import { N00dles } from "../../utils/helpers/N00dles";
import { Exploit } from "../../Exploits/Exploit";
import { applyAugmentation } from "../../Augmentation/AugmentationHelpers";
@@ -92,7 +92,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
function renderNoodleBar(): React.ReactElement {
function EatNoodles(): void {
SnackbarEvents.emit("You ate some delicious noodles and feel refreshed", "success", 2000);
SnackbarEvents.emit("You ate some delicious noodles and feel refreshed", ToastVariant.SUCCESS, 2000);
N00dles(); // This is the true power of the noodles.
if (player.sourceFiles.length > 0) player.giveExploit(Exploit.N00dles);
if (player.sourceFileLvl(5) > 0 || player.bitNodeN === 5) {
@@ -175,10 +175,11 @@ export function SpecialLocation(props: IProps): React.ReactElement {
applyAugmentation({ name: AugmentationNames.StaneksGift1, level: 1 });
}
router.toFaction(faction);
router.toStaneksGift();
}
function renderCotMG(): React.ReactElement {
const toStanek = <Button onClick={() => router.toStaneksGift()}>Open Stanek's Gift</Button>;
// prettier-ignore
const symbol = <Typography sx={{ lineHeight: '1em', whiteSpace: 'pre' }}>
{" `` "}<br />
@@ -219,6 +220,9 @@ export function SpecialLocation(props: IProps): React.ReactElement {
seems. Curious, Just how much of a machine's soul do you house in that body?
</i>
</Typography>
<br />
{toStanek}
<br />
{symbol}
</>
);
@@ -233,6 +237,9 @@ export function SpecialLocation(props: IProps): React.ReactElement {
mastery of the gift clearly demonstrates that. My hopes are climbing by the day for you.
</i>
</Typography>
<br />
{toStanek}
<br />
{symbol}
</>
);
@@ -243,6 +250,9 @@ export function SpecialLocation(props: IProps): React.ReactElement {
<Typography>
<i>Allison "Mother" Stanek: Welcome back my child!</i>
</Typography>
<br />
{toStanek}
<br />
{symbol}
</>
);
+26 -35
View File
@@ -1,9 +1,9 @@
import { Message } from "./Message";
import { Augmentations } from "../Augmentation/Augmentations";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { Router } from "../ui/GameRoot";
import { Programs } from "../Programs/Programs";
import { Player } from "../Player";
import { redPillFlag } from "../RedPill";
import { Page } from "../ui/Router";
import { GetServer } from "../Server/AllServers";
import { Settings } from "../Settings/Settings";
import { dialogBoxCreate } from "../ui/React/DialogBox";
@@ -51,7 +51,7 @@ function addMessageToServer(msg: Message, serverHostname: string): void {
//Checks if any of the 'timed' messages should be sent
function checkForMessagesToSend(): void {
if (redPillFlag) return;
if (Router.page() === Page.BitVerse) return;
const jumper0 = Messages[MessageFilenames.Jumper0];
const jumper1 = Messages[MessageFilenames.Jumper1];
const jumper2 = Messages[MessageFilenames.Jumper2];
@@ -62,38 +62,29 @@ function checkForMessagesToSend(): void {
const bitrunnersTest = Messages[MessageFilenames.BitRunnersTest];
const redpill = Messages[MessageFilenames.RedPill];
let redpillOwned = false;
if (Augmentations[AugmentationNames.TheRedPill].owned) {
redpillOwned = true;
}
if (redpill && redpillOwned && Player.sourceFiles.length === 0 && !redPillFlag) {
sendMessage(redpill, true);
} else if (redpill && redpillOwned) {
//If player has already destroyed a BitNode, message is not forced
if (!redPillFlag) {
sendMessage(redpill);
}
} else if (jumper0 && !jumper0.recvd && Player.hacking >= 25) {
if (Player.hasAugmentation(AugmentationNames.TheRedPill)) {
//Force the message if the player has never destroyed a BitNode
sendMessage(redpill, Player.sourceFiles.length === 0);
} else if (!jumper0.recvd && Player.hacking >= 25) {
sendMessage(jumper0);
const flightName = Programs.Flight.name;
const homeComp = Player.getHomeComputer();
if (!homeComp.programs.includes(flightName)) {
homeComp.programs.push(flightName);
}
} else if (jumper1 && !jumper1.recvd && Player.hacking >= 40) {
} else if (!jumper1.recvd && Player.hacking >= 40) {
sendMessage(jumper1);
} else if (cybersecTest && !cybersecTest.recvd && Player.hacking >= 50) {
} else if (!cybersecTest.recvd && Player.hacking >= 50) {
sendMessage(cybersecTest);
} else if (jumper2 && !jumper2.recvd && Player.hacking >= 175) {
} else if (!jumper2.recvd && Player.hacking >= 175) {
sendMessage(jumper2);
} else if (nitesecTest && !nitesecTest.recvd && Player.hacking >= 200) {
} else if (!nitesecTest.recvd && Player.hacking >= 200) {
sendMessage(nitesecTest);
} else if (jumper3 && !jumper3.recvd && Player.hacking >= 350) {
} else if (!jumper3.recvd && Player.hacking >= 350) {
sendMessage(jumper3);
} else if (jumper4 && !jumper4.recvd && Player.hacking >= 490) {
} else if (!jumper4.recvd && Player.hacking >= 490) {
sendMessage(jumper4);
} else if (bitrunnersTest && !bitrunnersTest.recvd && Player.hacking >= 500) {
} else if (!bitrunnersTest.recvd && Player.hacking >= 500) {
sendMessage(bitrunnersTest);
}
}
@@ -102,23 +93,23 @@ function AddToAllMessages(msg: Message): void {
Messages[msg.filename] = msg;
}
let Messages: { [key: string]: Message | undefined } = {};
let Messages: { [key: string]: Message } = {};
function loadMessages(saveString: string): void {
Messages = JSON.parse(saveString, Reviver);
}
const MessageFilenames = {
Jumper0: "j0.msg",
Jumper1: "j1.msg",
Jumper2: "j2.msg",
Jumper3: "j3.msg",
Jumper4: "j4.msg",
CyberSecTest: "csec-test.msg",
NiteSecTest: "nitesec-test.msg",
BitRunnersTest: "19dfj3l1nd.msg",
RedPill: "icarus.msg",
};
enum MessageFilenames {
Jumper0 = "j0.msg",
Jumper1 = "j1.msg",
Jumper2 = "j2.msg",
Jumper3 = "j3.msg",
Jumper4 = "j4.msg",
CyberSecTest = "csec-test.msg",
NiteSecTest = "nitesec-test.msg",
BitRunnersTest = "19dfj3l1nd.msg",
RedPill = "icarus.msg",
}
function initMessages(): void {
//Reset
+157
View File
@@ -0,0 +1,157 @@
import { getRamCost } from "./RamCostGenerator";
import type { IPort } from "../NetscriptPort";
import type { BaseServer } from "../Server/BaseServer";
import type { WorkerScript } from "./WorkerScript";
import { makeRuntimeRejectMsg } from "../NetscriptEvaluator";
import { Player } from "../Player";
import { CityName } from "src/Locations/data/CityNames";
type ExternalFunction = (...args: any[]) => any;
type ExternalAPI = {
[string: string]: ExternalAPI | ExternalFunction;
};
type InternalFunction<F extends (...args: unknown[]) => unknown> = (ctx: NetscriptContext) => F;
export type InternalAPI<API> = {
[Property in keyof API]: API[Property] extends ExternalFunction
? InternalFunction<API[Property]>
: API[Property] extends ExternalAPI
? InternalAPI<API[Property]>
: never;
};
type WrappedNetscriptFunction = (...args: unknown[]) => unknown;
type WrappedNetscriptAPI = {
readonly [string: string]: WrappedNetscriptAPI | WrappedNetscriptFunction;
};
export type NetscriptContext = {
makeRuntimeErrorMsg: (message: string) => string;
log: (message: () => string) => void;
workerScript: WorkerScript;
function: string;
helper: WrappedNetscriptHelpers;
};
type NetscriptHelpers = {
updateDynamicRam: (fnName: string, ramCost: number) => void;
makeRuntimeErrorMsg: (caller: string, msg: string) => string;
string: (funcName: string, argName: string, v: unknown) => string;
number: (funcName: string, argName: string, v: unknown) => number;
city: (funcName: string, argName: string, v: unknown) => CityName;
boolean: (v: unknown) => boolean;
getServer: (hostname: string, callingFnName: string) => BaseServer;
checkSingularityAccess: (func: string) => void;
hack: (hostname: any, manual: any, { threads: requestedThreads, stock }?: any) => Promise<number>;
getValidPort: (funcName: string, port: any) => IPort;
};
type WrappedNetscriptHelpers = {
makeRuntimeErrorMsg: (msg: string) => string;
string: (argName: string, v: unknown) => string;
number: (argName: string, v: unknown) => number;
city: (argName: string, v: unknown) => CityName;
boolean: (v: unknown) => boolean;
getServer: (hostname: string) => BaseServer;
checkSingularityAccess: () => void;
hack: (hostname: any, manual: any, { threads: requestedThreads, stock }?: any) => Promise<number>;
getValidPort: (port: any) => IPort;
};
function wrapFunction(
helpers: NetscriptHelpers,
wrappedAPI: any,
workerScript: WorkerScript,
func: (_ctx: NetscriptContext) => (...args: unknown[]) => unknown,
...tree: string[]
): void {
const functionPath = tree.join(".");
const functionName = tree.pop();
if (typeof functionName !== "string") {
throw makeRuntimeRejectMsg(workerScript, "Failure occured while wrapping netscript api");
}
const ctx = {
makeRuntimeErrorMsg: (message: string) => {
return helpers.makeRuntimeErrorMsg(functionPath, message);
},
log: (message: () => string) => {
workerScript.log(functionPath, message);
},
workerScript,
function: functionName,
helper: {
makeRuntimeErrorMsg: (msg: string) => helpers.makeRuntimeErrorMsg(functionPath, msg),
string: (argName: string, v: unknown) => helpers.string(functionPath, argName, v),
number: (argName: string, v: unknown) => helpers.number(functionPath, argName, v),
city: (argName: string, v: unknown) => helpers.city(functionPath, argName, v),
boolean: helpers.boolean,
getServer: (hostname: string) => helpers.getServer(hostname, functionPath),
checkSingularityAccess: () => helpers.checkSingularityAccess(functionName),
hack: helpers.hack,
getValidPort: (port: any) => helpers.getValidPort(functionPath, port),
},
};
function wrappedFunction(...args: unknown[]): unknown {
helpers.updateDynamicRam(ctx.function, getRamCost(Player, ...tree, ctx.function));
return func(ctx)(...args);
}
const parent = getNestedProperty(wrappedAPI, ...tree);
Object.defineProperty(parent, functionName, {
value: wrappedFunction,
writable: true,
enumerable: true,
});
}
export function wrapAPI(
helpers: NetscriptHelpers,
wrappedAPI: ExternalAPI,
workerScript: WorkerScript,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
namespace: any,
...tree: string[]
): WrappedNetscriptAPI {
if (typeof namespace !== "object") throw new Error("Invalid namespace?");
for (const property of Object.getOwnPropertyNames(namespace)) {
switch (typeof namespace[property]) {
case "function": {
wrapFunction(helpers, wrappedAPI, workerScript, namespace[property], ...tree, property);
break;
}
case "object": {
wrapAPI(helpers, wrappedAPI, workerScript, namespace[property], ...tree, property);
break;
}
default: {
setNestedProperty(wrappedAPI, namespace[property], ...tree, property);
}
}
}
return wrappedAPI;
}
function setNestedProperty(root: any, value: any, ...tree: string[]): any {
let target = root;
const key = tree.pop();
if (typeof key !== "string") {
throw new Error("Failure occured while wrapping netscript api (setNestedProperty)");
}
for (const branch of tree) {
if (target[branch] === undefined) {
target[branch] = {};
}
target = target[branch];
}
target[key] = value;
}
function getNestedProperty(root: any, ...tree: string[]): any {
let target = root;
for (const branch of tree) {
if (target[branch] === undefined) {
target[branch] = {};
}
target = target[branch];
}
return target;
}
+276 -254
View File
@@ -15,6 +15,7 @@ export const RamCostConstants: IMap<number> = {
ScriptWeakenRamCost: 0.15,
ScriptWeakenAnalyzeRamCost: 1,
ScriptScanRamCost: 0.2,
ScriptRecentScriptsRamCost: 0.2,
ScriptPortProgramRamCost: 0.05,
ScriptRunRamCost: 1.0,
ScriptExecRamCost: 1.3,
@@ -81,148 +82,52 @@ function SF4Cost(cost: number): (player: IPlayer) => number {
};
}
export const RamCosts: IMap<any> = {
hacknet: {
numNodes: 0,
purchaseNode: 0,
getPurchaseNodeCost: 0,
getNodeStats: 0,
upgradeLevel: 0,
upgradeRam: 0,
upgradeCore: 0,
upgradeCache: 0,
getLevelUpgradeCost: 0,
getRamUpgradeCost: 0,
getCoreUpgradeCost: 0,
getCacheUpgradeCost: 0,
numHashes: 0,
hashCost: 0,
spendHashes: 0,
},
sprintf: 0,
vsprintf: 0,
scan: RamCostConstants.ScriptScanRamCost,
hack: RamCostConstants.ScriptHackRamCost,
hackAnalyzeThreads: RamCostConstants.ScriptHackAnalyzeRamCost,
hackAnalyze: RamCostConstants.ScriptHackAnalyzeRamCost,
hackAnalyzeSecurity: RamCostConstants.ScriptHackAnalyzeRamCost,
hackAnalyzeChance: RamCostConstants.ScriptHackAnalyzeRamCost,
sleep: 0,
asleep: 0,
share: 2.4,
getSharePower: 0.2,
grow: RamCostConstants.ScriptGrowRamCost,
growthAnalyze: RamCostConstants.ScriptGrowthAnalyzeRamCost,
growthAnalyzeSecurity: RamCostConstants.ScriptGrowthAnalyzeRamCost,
weaken: RamCostConstants.ScriptWeakenRamCost,
weakenAnalyze: RamCostConstants.ScriptWeakenAnalyzeRamCost,
print: 0,
printf: 0,
tprint: 0,
clearLog: 0,
disableLog: 0,
enableLog: 0,
isLogEnabled: 0,
getScriptLogs: 0,
nuke: RamCostConstants.ScriptPortProgramRamCost,
brutessh: RamCostConstants.ScriptPortProgramRamCost,
ftpcrack: RamCostConstants.ScriptPortProgramRamCost,
relaysmtp: RamCostConstants.ScriptPortProgramRamCost,
httpworm: RamCostConstants.ScriptPortProgramRamCost,
sqlinject: RamCostConstants.ScriptPortProgramRamCost,
run: RamCostConstants.ScriptRunRamCost,
exec: RamCostConstants.ScriptExecRamCost,
spawn: RamCostConstants.ScriptSpawnRamCost,
kill: RamCostConstants.ScriptKillRamCost,
killall: RamCostConstants.ScriptKillRamCost,
exit: 0,
atExit: 0,
scp: RamCostConstants.ScriptScpRamCost,
ls: RamCostConstants.ScriptScanRamCost,
ps: RamCostConstants.ScriptScanRamCost,
hasRootAccess: RamCostConstants.ScriptHasRootAccessRamCost,
getIp: RamCostConstants.ScriptGetHostnameRamCost,
getHostname: RamCostConstants.ScriptGetHostnameRamCost,
getHackingLevel: RamCostConstants.ScriptGetHackingLevelRamCost,
getHackingMultipliers: RamCostConstants.ScriptGetMultipliersRamCost,
getHacknetMultipliers: RamCostConstants.ScriptGetMultipliersRamCost,
getBitNodeMultipliers: RamCostConstants.ScriptGetMultipliersRamCost,
getServer: RamCostConstants.ScriptGetMultipliersRamCost / 2,
getServerMoneyAvailable: RamCostConstants.ScriptGetServerRamCost,
getServerSecurityLevel: RamCostConstants.ScriptGetServerRamCost,
getServerBaseSecurityLevel: RamCostConstants.ScriptGetServerRamCost,
getServerMinSecurityLevel: RamCostConstants.ScriptGetServerRamCost,
getServerRequiredHackingLevel: RamCostConstants.ScriptGetServerRamCost,
getServerMaxMoney: RamCostConstants.ScriptGetServerRamCost,
getServerGrowth: RamCostConstants.ScriptGetServerRamCost,
getServerNumPortsRequired: RamCostConstants.ScriptGetServerRamCost,
getServerRam: RamCostConstants.ScriptGetServerRamCost,
getServerMaxRam: RamCostConstants.ScriptGetServerMaxRam,
getServerUsedRam: RamCostConstants.ScriptGetServerUsedRam,
serverExists: RamCostConstants.ScriptGetServerRamCost,
fileExists: RamCostConstants.ScriptFileExistsRamCost,
isRunning: RamCostConstants.ScriptIsRunningRamCost,
stock: {
getSymbols: RamCostConstants.ScriptGetStockRamCost,
getPrice: RamCostConstants.ScriptGetStockRamCost,
getAskPrice: RamCostConstants.ScriptGetStockRamCost,
getBidPrice: RamCostConstants.ScriptGetStockRamCost,
getPosition: RamCostConstants.ScriptGetStockRamCost,
getMaxShares: RamCostConstants.ScriptGetStockRamCost,
getPurchaseCost: RamCostConstants.ScriptGetStockRamCost,
getSaleGain: RamCostConstants.ScriptGetStockRamCost,
buy: RamCostConstants.ScriptBuySellStockRamCost,
sell: RamCostConstants.ScriptBuySellStockRamCost,
short: RamCostConstants.ScriptBuySellStockRamCost,
sellShort: RamCostConstants.ScriptBuySellStockRamCost,
placeOrder: RamCostConstants.ScriptBuySellStockRamCost,
cancelOrder: RamCostConstants.ScriptBuySellStockRamCost,
getOrders: RamCostConstants.ScriptBuySellStockRamCost,
getVolatility: RamCostConstants.ScriptBuySellStockRamCost,
getForecast: RamCostConstants.ScriptBuySellStockRamCost,
purchase4SMarketData: RamCostConstants.ScriptBuySellStockRamCost,
purchase4SMarketDataTixApi: RamCostConstants.ScriptBuySellStockRamCost,
purchaseWseAccount: RamCostConstants.ScriptBuySellStockRamCost,
purchaseTixApi: RamCostConstants.ScriptBuySellStockRamCost,
},
getPurchasedServerLimit: RamCostConstants.ScriptGetPurchasedServerLimit,
getPurchasedServerMaxRam: RamCostConstants.ScriptGetPurchasedServerMaxRam,
getPurchasedServerCost: RamCostConstants.ScriptGetPurchaseServerRamCost,
purchaseServer: RamCostConstants.ScriptPurchaseServerRamCost,
deleteServer: RamCostConstants.ScriptPurchaseServerRamCost,
getPurchasedServers: RamCostConstants.ScriptPurchaseServerRamCost,
write: 0,
tryWritePort: 0,
read: 0,
peek: 0,
clear: 0,
writePort: 0,
readPort: 0,
getPortHandle: 0,
rm: RamCostConstants.ScriptReadWriteRamCost,
scriptRunning: RamCostConstants.ScriptArbScriptRamCost,
scriptKill: RamCostConstants.ScriptArbScriptRamCost,
getScriptName: 0,
getScriptRam: RamCostConstants.ScriptGetScriptRamCost,
getHackTime: RamCostConstants.ScriptGetHackTimeRamCost,
getGrowTime: RamCostConstants.ScriptGetHackTimeRamCost,
getWeakenTime: RamCostConstants.ScriptGetHackTimeRamCost,
getScriptIncome: RamCostConstants.ScriptGetScriptRamCost,
getScriptExpGain: RamCostConstants.ScriptGetScriptRamCost,
getRunningScript: RamCostConstants.ScriptGetRunningScriptRamCost,
nFormat: 0,
tFormat: 0,
getTimeSinceLastAug: RamCostConstants.ScriptGetHackTimeRamCost,
prompt: 0,
wget: 0,
getFavorToDonate: RamCostConstants.ScriptGetFavorToDonate,
getPlayer: RamCostConstants.ScriptSingularityFn1RamCost / 4,
mv: 0,
getOwnedSourceFiles: RamCostConstants.ScriptGetOwnedSourceFiles,
tail: 0,
toast: 0,
// Hacknet API
const hacknet: IMap<any> = {
numNodes: 0,
purchaseNode: 0,
getPurchaseNodeCost: 0,
getNodeStats: 0,
upgradeLevel: 0,
upgradeRam: 0,
upgradeCore: 0,
upgradeCache: 0,
getLevelUpgradeCost: 0,
getRamUpgradeCost: 0,
getCoreUpgradeCost: 0,
getCacheUpgradeCost: 0,
numHashes: 0,
hashCost: 0,
spendHashes: 0,
};
// Singularity Functions
// Stock API
const stock: IMap<any> = {
getSymbols: RamCostConstants.ScriptGetStockRamCost,
getPrice: RamCostConstants.ScriptGetStockRamCost,
getAskPrice: RamCostConstants.ScriptGetStockRamCost,
getBidPrice: RamCostConstants.ScriptGetStockRamCost,
getPosition: RamCostConstants.ScriptGetStockRamCost,
getMaxShares: RamCostConstants.ScriptGetStockRamCost,
getPurchaseCost: RamCostConstants.ScriptGetStockRamCost,
getSaleGain: RamCostConstants.ScriptGetStockRamCost,
buy: RamCostConstants.ScriptBuySellStockRamCost,
sell: RamCostConstants.ScriptBuySellStockRamCost,
short: RamCostConstants.ScriptBuySellStockRamCost,
sellShort: RamCostConstants.ScriptBuySellStockRamCost,
placeOrder: RamCostConstants.ScriptBuySellStockRamCost,
cancelOrder: RamCostConstants.ScriptBuySellStockRamCost,
getOrders: RamCostConstants.ScriptBuySellStockRamCost,
getVolatility: RamCostConstants.ScriptBuySellStockRamCost,
getForecast: RamCostConstants.ScriptBuySellStockRamCost,
purchase4SMarketData: RamCostConstants.ScriptBuySellStockRamCost,
purchase4SMarketDataTixApi: RamCostConstants.ScriptBuySellStockRamCost,
purchaseWseAccount: RamCostConstants.ScriptBuySellStockRamCost,
purchaseTixApi: RamCostConstants.ScriptBuySellStockRamCost,
};
// Singularity API
const singularity: IMap<any> = {
universityCourse: SF4Cost(RamCostConstants.ScriptSingularityFn1RamCost),
gymWorkout: SF4Cost(RamCostConstants.ScriptSingularityFn1RamCost),
travelToCity: SF4Cost(RamCostConstants.ScriptSingularityFn1RamCost),
@@ -272,127 +177,244 @@ export const RamCosts: IMap<any> = {
installAugmentations: SF4Cost(RamCostConstants.ScriptSingularityFn3RamCost),
isFocused: SF4Cost(0.1),
setFocus: SF4Cost(0.1),
};
// Gang API
gang: {
createGang: RamCostConstants.ScriptGangApiBaseRamCost / 4,
inGang: RamCostConstants.ScriptGangApiBaseRamCost / 4,
getMemberNames: RamCostConstants.ScriptGangApiBaseRamCost / 4,
getGangInformation: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getOtherGangInformation: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getMemberInformation: RamCostConstants.ScriptGangApiBaseRamCost / 2,
canRecruitMember: RamCostConstants.ScriptGangApiBaseRamCost / 4,
recruitMember: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getTaskNames: RamCostConstants.ScriptGangApiBaseRamCost / 4,
getTaskStats: RamCostConstants.ScriptGangApiBaseRamCost / 4,
setMemberTask: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getEquipmentNames: RamCostConstants.ScriptGangApiBaseRamCost / 4,
getEquipmentCost: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getEquipmentType: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getEquipmentStats: RamCostConstants.ScriptGangApiBaseRamCost / 2,
purchaseEquipment: RamCostConstants.ScriptGangApiBaseRamCost,
ascendMember: RamCostConstants.ScriptGangApiBaseRamCost,
getAscensionResult: RamCostConstants.ScriptGangApiBaseRamCost / 2,
setTerritoryWarfare: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getChanceToWinClash: RamCostConstants.ScriptGangApiBaseRamCost,
getBonusTime: 0,
},
// Gang API
const gang: IMap<any> = {
createGang: RamCostConstants.ScriptGangApiBaseRamCost / 4,
inGang: RamCostConstants.ScriptGangApiBaseRamCost / 4,
getMemberNames: RamCostConstants.ScriptGangApiBaseRamCost / 4,
getGangInformation: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getOtherGangInformation: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getMemberInformation: RamCostConstants.ScriptGangApiBaseRamCost / 2,
canRecruitMember: RamCostConstants.ScriptGangApiBaseRamCost / 4,
recruitMember: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getTaskNames: RamCostConstants.ScriptGangApiBaseRamCost / 4,
getTaskStats: RamCostConstants.ScriptGangApiBaseRamCost / 4,
setMemberTask: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getEquipmentNames: RamCostConstants.ScriptGangApiBaseRamCost / 4,
getEquipmentCost: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getEquipmentType: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getEquipmentStats: RamCostConstants.ScriptGangApiBaseRamCost / 2,
purchaseEquipment: RamCostConstants.ScriptGangApiBaseRamCost,
ascendMember: RamCostConstants.ScriptGangApiBaseRamCost,
getAscensionResult: RamCostConstants.ScriptGangApiBaseRamCost / 2,
setTerritoryWarfare: RamCostConstants.ScriptGangApiBaseRamCost / 2,
getChanceToWinClash: RamCostConstants.ScriptGangApiBaseRamCost,
getBonusTime: 0,
};
// Bladeburner API
bladeburner: {
getContractNames: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getOperationNames: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getBlackOpNames: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getBlackOpRank: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2,
getGeneralActionNames: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getSkillNames: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
startAction: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
stopBladeburnerAction: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2,
getCurrentAction: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 4,
getActionTime: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionEstimatedSuccessChance: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionRepGain: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionCountRemaining: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionMaxLevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionCurrentLevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionAutolevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setActionAutolevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setActionLevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getRank: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillPoints: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillLevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillUpgradeCost: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
upgradeSkill: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getTeamSize: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setTeamSize: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityEstimatedPopulation: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityCommunities: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityChaos: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCity: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
switchCity: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getStamina: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
joinBladeburnerFaction: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
joinBladeburnerDivision: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getBonusTime: 0,
},
// Bladeburner API
const bladeburner: IMap<any> = {
getContractNames: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getOperationNames: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getBlackOpNames: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getBlackOpRank: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2,
getGeneralActionNames: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
getSkillNames: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10,
startAction: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
stopBladeburnerAction: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2,
getCurrentAction: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 4,
getActionTime: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionEstimatedSuccessChance: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionRepGain: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionCountRemaining: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionMaxLevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionCurrentLevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionAutolevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setActionAutolevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setActionLevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getRank: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillPoints: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillLevel: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getSkillUpgradeCost: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
upgradeSkill: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getTeamSize: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
setTeamSize: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityEstimatedPopulation: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityCommunities: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCityChaos: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getCity: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
switchCity: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getStamina: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
joinBladeburnerFaction: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
joinBladeburnerDivision: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getBonusTime: 0,
};
// Coding Contract API
codingcontract: {
attempt: RamCostConstants.ScriptCodingContractBaseRamCost,
getContractType: RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getData: RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getDescription: RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getNumTriesRemaining: RamCostConstants.ScriptCodingContractBaseRamCost / 5,
},
// Coding Contract API
const codingcontract: IMap<any> = {
attempt: RamCostConstants.ScriptCodingContractBaseRamCost,
getContractType: RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getData: RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getDescription: RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getNumTriesRemaining: RamCostConstants.ScriptCodingContractBaseRamCost / 5,
};
// Duplicate Sleeve API
sleeve: {
getNumSleeves: RamCostConstants.ScriptSleeveBaseRamCost,
setToShockRecovery: RamCostConstants.ScriptSleeveBaseRamCost,
setToSynchronize: RamCostConstants.ScriptSleeveBaseRamCost,
setToCommitCrime: RamCostConstants.ScriptSleeveBaseRamCost,
setToUniversityCourse: RamCostConstants.ScriptSleeveBaseRamCost,
travel: RamCostConstants.ScriptSleeveBaseRamCost,
setToCompanyWork: RamCostConstants.ScriptSleeveBaseRamCost,
setToFactionWork: RamCostConstants.ScriptSleeveBaseRamCost,
setToGymWorkout: RamCostConstants.ScriptSleeveBaseRamCost,
getSleeveStats: RamCostConstants.ScriptSleeveBaseRamCost,
getTask: RamCostConstants.ScriptSleeveBaseRamCost,
getInformation: RamCostConstants.ScriptSleeveBaseRamCost,
getSleeveAugmentations: RamCostConstants.ScriptSleeveBaseRamCost,
getSleevePurchasableAugs: RamCostConstants.ScriptSleeveBaseRamCost,
purchaseSleeveAug: RamCostConstants.ScriptSleeveBaseRamCost,
},
// Duplicate Sleeve API
const sleeve: IMap<any> = {
getNumSleeves: RamCostConstants.ScriptSleeveBaseRamCost,
setToShockRecovery: RamCostConstants.ScriptSleeveBaseRamCost,
setToSynchronize: RamCostConstants.ScriptSleeveBaseRamCost,
setToCommitCrime: RamCostConstants.ScriptSleeveBaseRamCost,
setToUniversityCourse: RamCostConstants.ScriptSleeveBaseRamCost,
travel: RamCostConstants.ScriptSleeveBaseRamCost,
setToCompanyWork: RamCostConstants.ScriptSleeveBaseRamCost,
setToFactionWork: RamCostConstants.ScriptSleeveBaseRamCost,
setToGymWorkout: RamCostConstants.ScriptSleeveBaseRamCost,
getSleeveStats: RamCostConstants.ScriptSleeveBaseRamCost,
getTask: RamCostConstants.ScriptSleeveBaseRamCost,
getInformation: RamCostConstants.ScriptSleeveBaseRamCost,
getSleeveAugmentations: RamCostConstants.ScriptSleeveBaseRamCost,
getSleevePurchasableAugs: RamCostConstants.ScriptSleeveBaseRamCost,
purchaseSleeveAug: RamCostConstants.ScriptSleeveBaseRamCost,
};
stanek: {
giftWidth: RamCostConstants.ScriptStanekWidth,
giftHeight: RamCostConstants.ScriptStanekHeight,
chargeFragment: RamCostConstants.ScriptStanekCharge,
fragmentDefinitions: RamCostConstants.ScriptStanekFragmentDefinitions,
activeFragments: RamCostConstants.ScriptStanekPlacedFragments,
clearGift: RamCostConstants.ScriptStanekClear,
canPlaceFragment: RamCostConstants.ScriptStanekCanPlace,
placeFragment: RamCostConstants.ScriptStanekPlace,
getFragment: RamCostConstants.ScriptStanekFragmentAt,
removeFragment: RamCostConstants.ScriptStanekDeleteAt,
},
// Stanek API
const stanek: IMap<any> = {
giftWidth: RamCostConstants.ScriptStanekWidth,
giftHeight: RamCostConstants.ScriptStanekHeight,
chargeFragment: RamCostConstants.ScriptStanekCharge,
fragmentDefinitions: RamCostConstants.ScriptStanekFragmentDefinitions,
activeFragments: RamCostConstants.ScriptStanekPlacedFragments,
clearGift: RamCostConstants.ScriptStanekClear,
canPlaceFragment: RamCostConstants.ScriptStanekCanPlace,
placeFragment: RamCostConstants.ScriptStanekPlace,
getFragment: RamCostConstants.ScriptStanekFragmentAt,
removeFragment: RamCostConstants.ScriptStanekDeleteAt,
};
ui: {
getTheme: 0,
setTheme: 0,
resetTheme: 0,
getStyles: 0,
setStyles: 0,
resetStyles: 0,
getGameInfo: 0,
},
// UI API
const ui: IMap<any> = {
getTheme: 0,
setTheme: 0,
resetTheme: 0,
getStyles: 0,
setStyles: 0,
resetStyles: 0,
getGameInfo: 0,
};
grafting: {
getAugmentationGraftPrice: 3.75,
getAugmentationGraftTime: 3.75,
graftAugmentation: 7.5,
},
// Grafting API
const grafting: IMap<any> = {
getAugmentationGraftPrice: 3.75,
getAugmentationGraftTime: 3.75,
graftAugmentation: 7.5,
};
export const RamCosts: IMap<any> = {
hacknet,
stock,
singularity,
...singularity, // singularity is in namespace & toplevel
gang,
bladeburner,
codingcontract,
sleeve,
stanek,
ui,
grafting,
sprintf: 0,
vsprintf: 0,
scan: RamCostConstants.ScriptScanRamCost,
hack: RamCostConstants.ScriptHackRamCost,
hackAnalyzeThreads: RamCostConstants.ScriptHackAnalyzeRamCost,
hackAnalyze: RamCostConstants.ScriptHackAnalyzeRamCost,
hackAnalyzeSecurity: RamCostConstants.ScriptHackAnalyzeRamCost,
hackAnalyzeChance: RamCostConstants.ScriptHackAnalyzeRamCost,
sleep: 0,
asleep: 0,
share: 2.4,
getSharePower: 0.2,
grow: RamCostConstants.ScriptGrowRamCost,
growthAnalyze: RamCostConstants.ScriptGrowthAnalyzeRamCost,
growthAnalyzeSecurity: RamCostConstants.ScriptGrowthAnalyzeRamCost,
weaken: RamCostConstants.ScriptWeakenRamCost,
weakenAnalyze: RamCostConstants.ScriptWeakenAnalyzeRamCost,
print: 0,
printf: 0,
tprint: 0,
clearLog: 0,
disableLog: 0,
enableLog: 0,
isLogEnabled: 0,
getScriptLogs: 0,
nuke: RamCostConstants.ScriptPortProgramRamCost,
brutessh: RamCostConstants.ScriptPortProgramRamCost,
ftpcrack: RamCostConstants.ScriptPortProgramRamCost,
relaysmtp: RamCostConstants.ScriptPortProgramRamCost,
httpworm: RamCostConstants.ScriptPortProgramRamCost,
sqlinject: RamCostConstants.ScriptPortProgramRamCost,
run: RamCostConstants.ScriptRunRamCost,
exec: RamCostConstants.ScriptExecRamCost,
spawn: RamCostConstants.ScriptSpawnRamCost,
kill: RamCostConstants.ScriptKillRamCost,
killall: RamCostConstants.ScriptKillRamCost,
exit: 0,
atExit: 0,
scp: RamCostConstants.ScriptScpRamCost,
ls: RamCostConstants.ScriptScanRamCost,
ps: RamCostConstants.ScriptScanRamCost,
getRecentScripts: RamCostConstants.ScriptRecentScriptsRamCost,
hasRootAccess: RamCostConstants.ScriptHasRootAccessRamCost,
getIp: RamCostConstants.ScriptGetHostnameRamCost,
getHostname: RamCostConstants.ScriptGetHostnameRamCost,
getHackingLevel: RamCostConstants.ScriptGetHackingLevelRamCost,
getHackingMultipliers: RamCostConstants.ScriptGetMultipliersRamCost,
getHacknetMultipliers: RamCostConstants.ScriptGetMultipliersRamCost,
getBitNodeMultipliers: RamCostConstants.ScriptGetMultipliersRamCost,
getServer: RamCostConstants.ScriptGetMultipliersRamCost / 2,
getServerMoneyAvailable: RamCostConstants.ScriptGetServerRamCost,
getServerSecurityLevel: RamCostConstants.ScriptGetServerRamCost,
getServerBaseSecurityLevel: RamCostConstants.ScriptGetServerRamCost,
getServerMinSecurityLevel: RamCostConstants.ScriptGetServerRamCost,
getServerRequiredHackingLevel: RamCostConstants.ScriptGetServerRamCost,
getServerMaxMoney: RamCostConstants.ScriptGetServerRamCost,
getServerGrowth: RamCostConstants.ScriptGetServerRamCost,
getServerNumPortsRequired: RamCostConstants.ScriptGetServerRamCost,
getServerRam: RamCostConstants.ScriptGetServerRamCost,
getServerMaxRam: RamCostConstants.ScriptGetServerMaxRam,
getServerUsedRam: RamCostConstants.ScriptGetServerUsedRam,
serverExists: RamCostConstants.ScriptGetServerRamCost,
fileExists: RamCostConstants.ScriptFileExistsRamCost,
isRunning: RamCostConstants.ScriptIsRunningRamCost,
getPurchasedServerLimit: RamCostConstants.ScriptGetPurchasedServerLimit,
getPurchasedServerMaxRam: RamCostConstants.ScriptGetPurchasedServerMaxRam,
getPurchasedServerCost: RamCostConstants.ScriptGetPurchaseServerRamCost,
purchaseServer: RamCostConstants.ScriptPurchaseServerRamCost,
deleteServer: RamCostConstants.ScriptPurchaseServerRamCost,
getPurchasedServers: RamCostConstants.ScriptPurchaseServerRamCost,
write: 0,
tryWritePort: 0,
read: 0,
peek: 0,
clear: 0,
writePort: 0,
readPort: 0,
getPortHandle: 0,
rm: RamCostConstants.ScriptReadWriteRamCost,
scriptRunning: RamCostConstants.ScriptArbScriptRamCost,
scriptKill: RamCostConstants.ScriptArbScriptRamCost,
getScriptName: 0,
getScriptRam: RamCostConstants.ScriptGetScriptRamCost,
getHackTime: RamCostConstants.ScriptGetHackTimeRamCost,
getGrowTime: RamCostConstants.ScriptGetHackTimeRamCost,
getWeakenTime: RamCostConstants.ScriptGetHackTimeRamCost,
getScriptIncome: RamCostConstants.ScriptGetScriptRamCost,
getScriptExpGain: RamCostConstants.ScriptGetScriptRamCost,
getRunningScript: RamCostConstants.ScriptGetRunningScriptRamCost,
nFormat: 0,
tFormat: 0,
getTimeSinceLastAug: RamCostConstants.ScriptGetHackTimeRamCost,
prompt: 0,
wget: 0,
getFavorToDonate: RamCostConstants.ScriptGetFavorToDonate,
getPlayer: RamCostConstants.ScriptSingularityFn1RamCost / 4,
mv: 0,
getOwnedSourceFiles: RamCostConstants.ScriptGetOwnedSourceFiles,
tail: 0,
toast: 0,
heart: {
// Easter egg function
break: 0,
+8 -11
View File
@@ -1,27 +1,24 @@
import { RunningScript } from "src/Script/RunningScript";
import { Settings } from "../Settings/Settings";
import { WorkerScript } from "./WorkerScript";
export const recentScripts: RecentScript[] = [];
export function AddRecentScript(workerScript: WorkerScript): void {
if (recentScripts.find((r) => r.pid === workerScript.pid)) return;
recentScripts.unshift({
filename: workerScript.name,
args: workerScript.args,
pid: workerScript.pid,
timestamp: new Date(),
if (recentScripts.find((r) => r.runningScript.pid === workerScript.pid)) return;
const killedTime = new Date();
recentScripts.unshift({
timeOfDeath: killedTime,
runningScript: workerScript.scriptRef,
});
while (recentScripts.length > 50) {
while (recentScripts.length > Settings.MaxRecentScriptsCapacity) {
recentScripts.pop();
}
}
export interface RecentScript {
filename: string;
args: string[];
pid: number;
timestamp: Date;
timeOfDeath: Date;
runningScript: RunningScript;
}
+37
View File
@@ -0,0 +1,37 @@
import { WorkerScript } from "./WorkerScript";
/**
* Script death marker.
*
* IMPORTANT: the game engine should not base any of it's decisions on the data
* carried in a ScriptDeath instance.
*
* This is because ScriptDeath instances are thrown through player code when a
* script is killed. Which grants the player access to the class and the ability
* to construct new instances with arbitrary data.
*/
export class ScriptDeath {
/** Process ID number. */
pid: number;
/** Filename of the script. */
name: string;
/** IP Address on which the script was running */
hostname: string;
/** Status message in case of script error. */
errorMessage = "";
constructor(ws: WorkerScript) {
this.pid = ws.pid;
this.name = ws.name;
this.hostname = ws.hostname;
this.errorMessage = ws.errorMessage;
Object.freeze(this);
}
}
Object.freeze(ScriptDeath);
Object.freeze(ScriptDeath.prototype);
+1 -1
View File
@@ -60,7 +60,7 @@ export class WorkerScript {
env: Environment;
/**
* Status message in case of script error. Currently unused I think
* Status message in case of script error.
*/
errorMessage = "";
+2 -1
View File
@@ -2,6 +2,7 @@
* Stops an actively-running script (represented by a WorkerScript object)
* and removes it from the global pool of active scripts.
*/
import { ScriptDeath } from "./ScriptDeath";
import { WorkerScript } from "./WorkerScript";
import { workerScripts } from "./WorkerScripts";
import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventEmitter";
@@ -139,7 +140,7 @@ function killNetscriptDelay(workerScript: WorkerScript): void {
if (workerScript.delay) {
clearTimeout(workerScript.delay);
if (workerScript.delayReject) {
workerScript.delayReject(workerScript);
workerScript.delayReject(new ScriptDeath(workerScript));
}
}
}
+6 -1
View File
@@ -1,15 +1,20 @@
import { isString } from "./utils/helpers/isString";
import { GetServer } from "./Server/AllServers";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { WorkerScript } from "./Netscript/WorkerScript";
export function netscriptDelay(time: number, workerScript: WorkerScript): Promise<void> {
// Cancel any pre-existing netscriptDelay'ed function call
// TODO: the rejection almost certainly ends up in the uncaught rejection handler.
// Maybe reject with a stack-trace'd error message?
if (workerScript.delayReject) workerScript.delayReject();
return new Promise(function (resolve, reject) {
workerScript.delay = window.setTimeout(() => {
workerScript.delay = null;
workerScript.delayReject = undefined;
if (workerScript.env.stopFlag) reject(workerScript);
if (workerScript.env.stopFlag) reject(new ScriptDeath(workerScript));
else resolve();
}, time);
workerScript.delayReject = reject;
+71 -24
View File
@@ -75,10 +75,13 @@ import { IPort } from "./NetscriptPort";
import {
NS as INS,
Singularity as ISingularity,
Player as INetscriptPlayer,
Gang as IGang,
Bladeburner as IBladeburner,
Stanek as IStanek,
RunningScript as IRunningScript,
RecentScript as IRecentScript,
SourceFileLvl,
BasicHGWOptions,
ProcessInfo,
@@ -87,18 +90,22 @@ import {
BitNodeMultipliers as IBNMults,
Server as IServerDef,
RunningScript as IRunningScriptDef,
// ToastVariant,
} from "./ScriptEditor/NetscriptDefinitions";
import { NetscriptSingularity } from "./NetscriptFunctions/Singularity";
import { toNative } from "./NetscriptFunctions/toNative";
import { dialogBoxCreate } from "./ui/React/DialogBox";
import { SnackbarEvents } from "./ui/React/Snackbar";
import { SnackbarEvents, ToastVariant } from "./ui/React/Snackbar";
import { checkEnum } from "./utils/helpers/checkEnum";
import { Flags } from "./NetscriptFunctions/Flags";
import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence";
import { CalculateShareMult, StartSharing } from "./NetworkShare/Share";
import { recentScripts } from "./Netscript/RecentScripts";
import { CityName } from "./Locations/data/CityNames";
import { wrapAPI } from "./Netscript/APIWrapper";
interface NS extends INS {
[key: string]: any;
@@ -212,6 +219,32 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return null;
};
/**
* Sanitizes a `RunningScript` to remove sensitive information, making it suitable for
* return through an NS function.
* @see NS.getRecentScripts
* @see NS.getRunningScript
* @param runningScript Existing, internal RunningScript
* @returns A sanitized, NS-facing copy of the RunningScript
*/
const createPublicRunningScript = function (runningScript: RunningScript): IRunningScript {
return {
args: runningScript.args.slice(),
filename: runningScript.filename,
logs: runningScript.logs.slice(),
offlineExpGained: runningScript.offlineExpGained,
offlineMoneyMade: runningScript.offlineMoneyMade,
offlineRunningTime: runningScript.offlineRunningTime,
onlineExpGained: runningScript.onlineExpGained,
onlineMoneyMade: runningScript.onlineMoneyMade,
onlineRunningTime: runningScript.onlineRunningTime,
pid: runningScript.pid,
ramUsage: runningScript.ramUsage,
server: runningScript.server,
threads: runningScript.threads,
};
};
/**
* Helper function for getting the error log message when the user specifies
* a nonexistent running script
@@ -491,12 +524,14 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const sleeve = NetscriptSleeve(Player, workerScript, helper);
const extra = NetscriptExtra(Player, workerScript, helper);
const hacknet = NetscriptHacknet(Player, workerScript, helper);
const stanek = NetscriptStanek(Player, workerScript, helper);
const stanek = wrapAPI(helper, {}, workerScript, NetscriptStanek(Player, workerScript, helper), "stanek")
.stanek as unknown as IStanek;
const bladeburner = NetscriptBladeburner(Player, workerScript, helper);
const codingcontract = NetscriptCodingContract(Player, workerScript, helper);
const corporation = NetscriptCorporation(Player, workerScript, helper);
const formulas = NetscriptFormulas(Player, workerScript, helper);
const singularity = NetscriptSingularity(Player, workerScript, helper);
const singularity = wrapAPI(helper, {}, workerScript, NetscriptSingularity(Player, workerScript), "singularity")
.singularity as unknown as ISingularity;
const stockmarket = NetscriptStockMarket(Player, workerScript, helper);
const ui = NetscriptUserInterface(Player, workerScript, helper);
const grafting = NetscriptGrafting(Player, workerScript, helper);
@@ -583,9 +618,25 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return calculatePercentMoneyHacked(server, Player);
},
hackAnalyzeSecurity: function (_threads: unknown): number {
hackAnalyzeSecurity: function (_threads: unknown, _hostname?: unknown): number {
updateDynamicRam("hackAnalyzeSecurity", getRamCost(Player, "hackAnalyzeSecurity"));
const threads = helper.number("hackAnalyzeSecurity", "threads", _threads);
let threads = helper.number("hackAnalyzeSecurity", "threads", _threads);
if (_hostname) {
const hostname = helper.string("hackAnalyzeSecurity", "hostname", _hostname);
const server = safeGetServer(hostname, "hackAnalyze");
if (!(server instanceof Server)) {
workerScript.log("hackAnalyzeSecurity", () => "Cannot be executed on this server.");
return 0;
}
const percentHacked = calculatePercentMoneyHacked(server, Player);
if (percentHacked > 0) {
// thread count is limited to the maximum number of threads needed
threads = Math.ceil(1 / percentHacked);
}
}
return CONSTANTS.ServerFortifyAmount * threads;
},
hackAnalyzeChance: function (_hostname: unknown): number {
@@ -1413,6 +1464,13 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
allFiles.sort();
return allFiles;
},
getRecentScripts: function (): IRecentScript[] {
updateDynamicRam("getRecentScripts", getRamCost(Player, "getRecentScripts"));
return recentScripts.map((rs) => ({
timeOfDeath: rs.timeOfDeath,
...createPublicRunningScript(rs.runningScript),
}));
},
ps: function (_hostname: unknown = workerScript.hostname): ProcessInfo[] {
updateDynamicRam("ps", getRamCost(Player, "ps"));
const hostname = helper.string("ps", "hostname", _hostname);
@@ -2128,21 +2186,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
runningScript = getRunningScript(fn, hostname, "getRunningScript", args);
}
if (runningScript === null) return null;
return {
args: runningScript.args.slice(),
filename: runningScript.filename,
logs: runningScript.logs.slice(),
offlineExpGained: runningScript.offlineExpGained,
offlineMoneyMade: runningScript.offlineMoneyMade,
offlineRunningTime: runningScript.offlineRunningTime,
onlineExpGained: runningScript.onlineExpGained,
onlineMoneyMade: runningScript.onlineMoneyMade,
onlineRunningTime: runningScript.onlineRunningTime,
pid: runningScript.pid,
ramUsage: runningScript.ramUsage,
server: runningScript.server,
threads: runningScript.threads,
};
return createPublicRunningScript(runningScript);
},
getHackTime: function (_hostname: unknown = workerScript.hostname): number {
updateDynamicRam("getHackTime", getRamCost(Player, "getHackTime"));
@@ -2262,13 +2306,13 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const message = helper.string("alert", "message", _message);
dialogBoxCreate(message);
},
toast: function (_message: unknown, _variant: unknown = "success", duration: any = 2000): void {
toast: function (_message: unknown, _variant: unknown = ToastVariant.SUCCESS, duration: any = 2000): void {
updateDynamicRam("toast", getRamCost(Player, "toast"));
const message = helper.string("toast", "message", _message);
const variant = helper.string("toast", "variant", _variant);
if (!["success", "info", "warning", "error"].includes(variant))
throw new Error(`variant must be one of "success", "info", "warning", or "error"`);
SnackbarEvents.emit(message, variant as any, duration);
if (!checkEnum(ToastVariant, variant))
throw new Error(`variant must be one of ${Object.values(ToastVariant).join(", ")}`);
SnackbarEvents.emit(message, variant, duration);
},
prompt: function (_txt: unknown, options?: { type?: string; options?: string[] }): Promise<boolean | string> {
updateDynamicRam("prompt", getRamCost(Player, "prompt"));
@@ -2511,6 +2555,9 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}
},
flags: Flags(workerScript.args),
enums: {
toast: ToastVariant,
},
};
// add undocumented functions
+24 -5
View File
@@ -356,11 +356,14 @@ export function NetscriptCorporation(
const cityName = helper.city("getMaterial", "cityName", _cityName);
const materialName = helper.string("getMaterial", "materialName", _materialName);
const material = getMaterial(divisionName, cityName, materialName);
const corporation = getCorporation();
return {
cost: material.bCost,
name: material.name,
qty: material.qty,
qlt: material.qlt,
dmd: corporation.unlockUpgrades[2] ? material.dmd : undefined,
cmp: corporation.unlockUpgrades[3] ? material.cmp : undefined,
prod: material.prd,
sell: material.sll,
};
@@ -370,10 +373,20 @@ export function NetscriptCorporation(
const divisionName = helper.string("getProduct", "divisionName", _divisionName);
const productName = helper.string("getProduct", "productName", _productName);
const product = getProduct(divisionName, productName);
const corporation = getCorporation();
return {
name: product.name,
dmd: product.dmd,
cmp: product.cmp,
dmd: corporation.unlockUpgrades[2] ? product.dmd : undefined,
cmp: corporation.unlockUpgrades[3] ? product.cmp : undefined,
rat: product.rat,
properties: {
qlt: product.qlt,
per: product.per,
dur: product.dur,
rel: product.rel,
aes: product.aes,
fea: product.fea,
},
pCost: product.pCost,
sCost: product.sCost,
cityData: product.data,
@@ -678,9 +691,6 @@ export function NetscriptCorporation(
const office = getOffice(divisionName, cityName);
if (!Object.values(EmployeePositions).includes(job)) throw new Error(`'${job}' is not a valid job.`);
return netscriptDelay(1000, workerScript).then(function () {
if (workerScript.env.stopFlag) {
return Promise.reject(workerScript);
}
return Promise.resolve(office.setEmployeeToJob(job, amount));
});
},
@@ -794,6 +804,15 @@ export function NetscriptCorporation(
"Research & Development": office.employeeProd[EmployeePositions.RandD],
Training: office.employeeProd[EmployeePositions.Training],
},
employeeJobs: {
Operations: office.employeeJobs[EmployeePositions.Operations],
Engineer: office.employeeJobs[EmployeePositions.Engineer],
Business: office.employeeJobs[EmployeePositions.Business],
Management: office.employeeJobs[EmployeePositions.Management],
"Research & Development": office.employeeJobs[EmployeePositions.RandD],
Training: office.employeeJobs[EmployeePositions.Training],
Unassigned: office.employeeJobs[EmployeePositions.Unassigned],
},
};
},
getEmployee: function (_divisionName: unknown, _cityName: unknown, _employeeName: unknown): NSEmployee {
+2 -2
View File
@@ -54,7 +54,7 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
if (player.city !== CityName.NewTokyo) {
throw helper.makeRuntimeErrorMsg(
"grafting.graftAugmentation",
"You must be in New Tokyo to begin crafting an Augmentation.",
"You must be in New Tokyo to begin grafting an Augmentation.",
);
}
if (!getAvailableAugs(player).includes(augName)) {
@@ -90,7 +90,7 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
Router.toTerminal();
}
workerScript.log("grafting.graftAugmentation", () => `Began crafting Augmentation ${augName}.`);
workerScript.log("grafting.graftAugmentation", () => `Began grafting Augmentation ${augName}.`);
return true;
},
};
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -97,7 +97,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
travel: function (_sleeveNumber: unknown, _cityName: unknown): boolean {
updateRam("travel");
const sleeveNumber = helper.number("travel", "sleeveNumber", _sleeveNumber);
const cityName = helper.string("setToUniversityCourse", "cityName", _cityName);
const cityName = helper.string("travel", "cityName", _cityName);
checkSleeveAPIAccess("travel");
checkSleeveNumber("travel", sleeveNumber);
return player.sleeves[sleeveNumber].travel(player, cityName as CityName);
@@ -105,7 +105,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
setToCompanyWork: function (_sleeveNumber: unknown, acompanyName: unknown): boolean {
updateRam("setToCompanyWork");
const sleeveNumber = helper.number("setToCompanyWork", "sleeveNumber", _sleeveNumber);
const companyName = helper.string("setToUniversityCourse", "companyName", acompanyName);
const companyName = helper.string("setToCompanyWork", "companyName", acompanyName);
checkSleeveAPIAccess("setToCompanyWork");
checkSleeveNumber("setToCompanyWork", sleeveNumber);
@@ -117,7 +117,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) {
throw helper.makeRuntimeErrorMsg(
"sleeve.setToFactionWork",
"sleeve.setToCompanyWork",
`Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`,
);
}
@@ -132,8 +132,8 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
): boolean | undefined {
updateRam("setToFactionWork");
const sleeveNumber = helper.number("setToFactionWork", "sleeveNumber", _sleeveNumber);
const factionName = helper.string("setToUniversityCourse", "factionName", _factionName);
const workType = helper.string("setToUniversityCourse", "workType", _workType);
const factionName = helper.string("setToFactionWork", "factionName", _factionName);
const workType = helper.string("setToFactionWork", "workType", _workType);
checkSleeveAPIAccess("setToFactionWork");
checkSleeveNumber("setToFactionWork", sleeveNumber);
@@ -163,8 +163,8 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
setToGymWorkout: function (_sleeveNumber: unknown, _gymName: unknown, _stat: unknown): boolean {
updateRam("setToGymWorkout");
const sleeveNumber = helper.number("setToGymWorkout", "sleeveNumber", _sleeveNumber);
const gymName = helper.string("setToUniversityCourse", "gymName", _gymName);
const stat = helper.string("setToUniversityCourse", "stat", _stat);
const gymName = helper.string("setToGymWorkout", "gymName", _gymName);
const stat = helper.string("setToGymWorkout", "stat", _stat);
checkSleeveAPIAccess("setToGymWorkout");
checkSleeveNumber("setToGymWorkout", sleeveNumber);
+90 -90
View File
@@ -2,112 +2,112 @@ import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { WorkerScript } from "../Netscript/WorkerScript";
import { netscriptDelay } from "../NetscriptEvaluator";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { staneksGift } from "../CotMG/Helper";
import { Fragments, FragmentById } from "../CotMG/Fragment";
import {
Stanek as IStanek,
Fragment as IFragment,
ActiveFragment as IActiveFragment,
Stanek as IStanek,
} from "../ScriptEditor/NetscriptDefinitions";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { NetscriptContext, InternalAPI } from "../Netscript/APIWrapper";
export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IStanek {
export function NetscriptStanek(
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): InternalAPI<IStanek> {
function checkStanekAPIAccess(func: string): void {
if (!player.hasAugmentation(AugmentationNames.StaneksGift1, true)) {
helper.makeRuntimeErrorMsg(func, "Requires Stanek's Gift installed.");
}
}
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "stanek", funcName));
return {
giftWidth: function (): number {
updateRam("giftWidth");
checkStanekAPIAccess("giftWidth");
return staneksGift.width();
},
giftHeight: function (): number {
updateRam("giftHeight");
checkStanekAPIAccess("giftHeight");
return staneksGift.height();
},
chargeFragment: function (_rootX: unknown, _rootY: unknown): Promise<void> {
updateRam("chargeFragment");
const rootX = helper.number("stanek.chargeFragment", "rootX", _rootX);
const rootY = helper.number("stanek.chargeFragment", "rootY", _rootY);
checkStanekAPIAccess("chargeFragment");
const fragment = staneksGift.findFragment(rootX, rootY);
if (!fragment)
throw helper.makeRuntimeErrorMsg("stanek.chargeFragment", `No fragment with root (${rootX}, ${rootY}).`);
const time = staneksGift.inBonus() ? 200 : 1000;
return netscriptDelay(time, workerScript).then(function () {
const charge = staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
workerScript.log("stanek.chargeFragment", () => `Charged fragment for ${charge} charge.`);
return Promise.resolve();
});
},
fragmentDefinitions: function (): IFragment[] {
updateRam("fragmentDefinitions");
checkStanekAPIAccess("fragmentDefinitions");
workerScript.log("stanek.fragmentDefinitions", () => `Returned ${Fragments.length} fragments`);
return Fragments.map((f) => f.copy());
},
activeFragments: function (): IActiveFragment[] {
updateRam("activeFragments");
checkStanekAPIAccess("activeFragments");
workerScript.log("stanek.activeFragments", () => `Returned ${staneksGift.fragments.length} fragments`);
return staneksGift.fragments.map((af) => {
return { ...af.copy(), ...af.fragment().copy() };
});
},
clearGift: function (): void {
updateRam("clearGift");
checkStanekAPIAccess("clearGift");
workerScript.log("stanek.clearGift", () => `Cleared Stanek's Gift.`);
staneksGift.clear();
},
canPlaceFragment: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
updateRam("canPlaceFragment");
const rootX = helper.number("stanek.canPlaceFragment", "rootX", _rootX);
const rootY = helper.number("stanek.canPlaceFragment", "rootY", _rootY);
const rotation = helper.number("stanek.canPlaceFragment", "rotation", _rotation);
const fragmentId = helper.number("stanek.canPlaceFragment", "fragmentId", _fragmentId);
checkStanekAPIAccess("canPlaceFragment");
const fragment = FragmentById(fragmentId);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.canPlaceFragment", `Invalid fragment id: ${fragmentId}`);
const can = staneksGift.canPlace(rootX, rootY, rotation, fragment);
return can;
},
placeFragment: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
updateRam("placeFragment");
const rootX = helper.number("stanek.placeFragment", "rootX", _rootX);
const rootY = helper.number("stanek.placeFragment", "rootY", _rootY);
const rotation = helper.number("stanek.placeFragment", "rotation", _rotation);
const fragmentId = helper.number("stanek.placeFragment", "fragmentId", _fragmentId);
checkStanekAPIAccess("placeFragment");
const fragment = FragmentById(fragmentId);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.placeFragment", `Invalid fragment id: ${fragmentId}`);
return staneksGift.place(rootX, rootY, rotation, fragment);
},
getFragment: function (_rootX: unknown, _rootY: unknown): IActiveFragment | undefined {
updateRam("getFragment");
const rootX = helper.number("stanek.getFragment", "rootX", _rootX);
const rootY = helper.number("stanek.getFragment", "rootY", _rootY);
checkStanekAPIAccess("getFragment");
const fragment = staneksGift.findFragment(rootX, rootY);
if (fragment !== undefined) return fragment.copy();
return undefined;
},
removeFragment: function (_rootX: unknown, _rootY: unknown): boolean {
updateRam("removeFragment");
const rootX = helper.number("stanek.removeFragment", "rootX", _rootX);
const rootY = helper.number("stanek.removeFragment", "rootY", _rootY);
checkStanekAPIAccess("removeFragment");
return staneksGift.delete(rootX, rootY);
},
giftWidth: () =>
function (): number {
checkStanekAPIAccess("giftWidth");
return staneksGift.width();
},
giftHeight: () =>
function (): number {
checkStanekAPIAccess("giftHeight");
return staneksGift.height();
},
chargeFragment: (_ctx: NetscriptContext) =>
function (_rootX: unknown, _rootY: unknown): Promise<void> {
const rootX = _ctx.helper.number("rootX", _rootX);
const rootY = _ctx.helper.number("rootY", _rootY);
checkStanekAPIAccess("chargeFragment");
const fragment = staneksGift.findFragment(rootX, rootY);
if (!fragment) throw _ctx.makeRuntimeErrorMsg(`No fragment with root (${rootX}, ${rootY}).`);
const time = staneksGift.inBonus() ? 200 : 1000;
return netscriptDelay(time, workerScript).then(function () {
const charge = staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
_ctx.log(() => `Charged fragment for ${charge} charge.`);
return Promise.resolve();
});
},
fragmentDefinitions: (_ctx: NetscriptContext) =>
function (): IFragment[] {
checkStanekAPIAccess("fragmentDefinitions");
_ctx.log(() => `Returned ${Fragments.length} fragments`);
return Fragments.map((f) => f.copy());
},
activeFragments: (_ctx: NetscriptContext) =>
function (): IActiveFragment[] {
checkStanekAPIAccess("activeFragments");
_ctx.log(() => `Returned ${staneksGift.fragments.length} fragments`);
return staneksGift.fragments.map((af) => {
return { ...af.copy(), ...af.fragment().copy() };
});
},
clearGift: (_ctx: NetscriptContext) =>
function (): void {
checkStanekAPIAccess("clearGift");
_ctx.log(() => `Cleared Stanek's Gift.`);
staneksGift.clear();
},
canPlaceFragment: (_ctx: NetscriptContext) =>
function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
const rootX = _ctx.helper.number("rootX", _rootX);
const rootY = _ctx.helper.number("rootY", _rootY);
const rotation = _ctx.helper.number("rotation", _rotation);
const fragmentId = _ctx.helper.number("fragmentId", _fragmentId);
checkStanekAPIAccess("canPlaceFragment");
const fragment = FragmentById(fragmentId);
if (!fragment) throw _ctx.makeRuntimeErrorMsg(`Invalid fragment id: ${fragmentId}`);
const can = staneksGift.canPlace(rootX, rootY, rotation, fragment);
return can;
},
placeFragment: (_ctx: NetscriptContext) =>
function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
const rootX = _ctx.helper.number("rootX", _rootX);
const rootY = _ctx.helper.number("rootY", _rootY);
const rotation = _ctx.helper.number("rotation", _rotation);
const fragmentId = _ctx.helper.number("fragmentId", _fragmentId);
checkStanekAPIAccess("placeFragment");
const fragment = FragmentById(fragmentId);
if (!fragment) throw _ctx.makeRuntimeErrorMsg(`Invalid fragment id: ${fragmentId}`);
return staneksGift.place(rootX, rootY, rotation, fragment);
},
getFragment: (_ctx: NetscriptContext) =>
function (_rootX: unknown, _rootY: unknown): IActiveFragment | undefined {
const rootX = _ctx.helper.number("rootX", _rootX);
const rootY = _ctx.helper.number("rootY", _rootY);
checkStanekAPIAccess("getFragment");
const fragment = staneksGift.findFragment(rootX, rootY);
if (fragment !== undefined) return fragment.copy();
return undefined;
},
removeFragment: (_ctx: NetscriptContext) =>
function (_rootX: unknown, _rootY: unknown): boolean {
const rootX = _ctx.helper.number("rootX", _rootX);
const rootY = _ctx.helper.number("rootY", _rootY);
checkStanekAPIAccess("removeFragment");
return staneksGift.delete(rootX, rootY);
},
};
}
+2 -1
View File
@@ -3,7 +3,7 @@ import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { buyStock, sellStock, shortStock, sellShort } from "../StockMarket/BuyingAndSelling";
import { StockMarket, SymbolToStockMap, placeOrder, cancelOrder } from "../StockMarket/StockMarket";
import { StockMarket, SymbolToStockMap, placeOrder, cancelOrder, initStockMarketFn } from "../StockMarket/StockMarket";
import { getBuyTransactionCost, getSellTransactionGain } from "../StockMarket/StockMarketHelpers";
import { OrderTypes } from "../StockMarket/data/OrderTypes";
import { PositionTypes } from "../StockMarket/data/PositionTypes";
@@ -411,6 +411,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
}
player.hasWseAccount = true;
initStockMarketFn();
player.loseMoney(getStockMarketWseCost(), "stock");
workerScript.log("stock.purchaseWseAccount", () => "Purchased WSE Account Access");
return true;
+1 -1
View File
@@ -202,7 +202,7 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
// We automatically define a print function() in the NetscriptJS module so that
// accidental calls to window.print() do not bring up the "print screen" dialog
transformedCode += `\n\nfunction print() {throw new Error("Invalid call to window.print(). Did you mean to use Netscript's print()?");}`;
transformedCode += `\n\nfunction print() {throw new Error("Invalid call to window.print(). Did you mean to use Netscript's print()?");}\n//# sourceURL=${script.server}/${script.filename}`;
const blob = URL.createObjectURL(makeScriptBlob(transformedCode));
// Push the blob URL onto the top of the stack.
+100 -84
View File
@@ -3,6 +3,7 @@
* that allows for scripts to run
*/
import { killWorkerScript } from "./Netscript/killWorkerScript";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { WorkerScript } from "./Netscript/WorkerScript";
import { workerScripts } from "./Netscript/WorkerScripts";
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
@@ -59,7 +60,7 @@ export function prestigeWorkerScripts(): void {
// JS script promises need a little massaging to have the same guarantees as netscript
// promises. This does said massaging and kicks the script off. It returns a promise
// that resolves or rejects when the corresponding worker script is done.
function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Promise<WorkerScript> {
function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Promise<void> {
workerScript.running = true;
// The name of the currently running netscript function, to prevent concurrent
@@ -79,7 +80,7 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
// This is not a problem for legacy Netscript because it also checks the
// stop flag in the evaluator.
if (workerScript.env.stopFlag) {
throw workerScript;
throw new ScriptDeath(workerScript);
}
if (propName === "asleep") return f(...args); // OK for multiple simultaneous calls to sleep.
@@ -90,7 +91,7 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
"promise-returning function? (Currently running: %s tried to run: %s)";
if (runningFn) {
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, sprintf(msg, runningFn, propName));
throw workerScript;
throw new ScriptDeath(workerScript);
}
runningFn = propName;
@@ -116,18 +117,29 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
};
}
for (const prop of Object.keys(workerScript.env.vars)) {
if (typeof workerScript.env.vars[prop] !== "function") continue;
workerScript.env.vars[prop] = wrap(prop, workerScript.env.vars[prop]);
function wrapObject(vars: any, ...tree: string[]): void {
for (const prop of Object.keys(vars)) {
switch (typeof vars[prop]) {
case "function": {
vars[prop] = wrap([...tree, prop].join("."), vars[prop]);
break;
}
case "object": {
if (Array.isArray(vars[prop])) continue;
wrapObject(vars[prop], ...tree, prop);
break;
}
}
}
}
workerScript.env.vars.stanek.charge = wrap("stanek.charge", workerScript.env.vars.stanek.charge);
wrapObject(workerScript.env.vars);
// Note: the environment that we pass to the JS script only needs to contain the functions visible
// to that script, which env.vars does at this point.
return new Promise<WorkerScript>((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
executeJSScript(player, workerScript.getServer().scripts, workerScript)
.then(() => {
resolve(workerScript);
resolve();
})
.catch((e) => reject(e));
}).catch((e) => {
@@ -140,20 +152,21 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
e.message + ((e.stack && "\nstack:\n" + e.stack.toString()) || ""),
);
}
throw workerScript;
throw new ScriptDeath(workerScript);
} else if (isScriptErrorMessage(e)) {
workerScript.errorMessage = e;
throw workerScript;
} else if (e instanceof WorkerScript) {
throw new ScriptDeath(workerScript);
} else if (e instanceof ScriptDeath) {
throw e;
}
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, e);
throw workerScript; // Don't know what to do with it, let's rethrow.
// Don't know what to do with it, let's try making an error message out of it
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, "" + e);
throw new ScriptDeath(workerScript);
});
}
function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript> {
function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
const code = workerScript.code;
workerScript.running = true;
@@ -168,7 +181,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return Promise.resolve(workerScript);
return Promise.resolve();
}
const interpreterInitialization = function (int: any, scope: any): void {
@@ -201,7 +214,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
})
.catch(function (err: any) {
// workerscript is when you cancel a delay
if (!(err instanceof WorkerScript)) {
if (!(err instanceof ScriptDeath)) {
console.error(err);
const errorTextArray = err.split("|DELIMITER|");
const hostname = errorTextArray[1];
@@ -214,7 +227,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return Promise.resolve(workerScript);
return Promise.resolve();
}
});
};
@@ -277,14 +290,14 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return Promise.resolve(workerScript);
return Promise.resolve();
}
return new Promise(function (resolve, reject) {
function runInterpreter(): void {
try {
if (workerScript.env.stopFlag) {
return reject(workerScript);
return reject(new ScriptDeath(workerScript));
}
let more = true;
@@ -297,7 +310,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
if (more) {
setTimeout(runInterpreter, Settings.CodeInstructionRunTime);
} else {
resolve(workerScript);
resolve();
}
} catch (e: any) {
e = e.toString();
@@ -305,7 +318,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
e = makeRuntimeRejectMsg(workerScript, e);
}
workerScript.errorMessage = e;
return reject(workerScript);
return reject(new ScriptDeath(workerScript));
}
}
@@ -314,11 +327,12 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
} catch (e: any) {
if (isString(e)) {
workerScript.errorMessage = e;
return reject(workerScript);
} else if (e instanceof WorkerScript) {
return reject(new ScriptDeath(workerScript));
} else if (e instanceof ScriptDeath) {
return reject(e);
} else {
return reject(workerScript);
console.error(e);
return reject(new ScriptDeath(workerScript));
}
}
});
@@ -541,83 +555,85 @@ function createAndAddWorkerScript(
// Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying
// RunningScript's PID as well
const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
s.ramUsage = oneRamUsage;
const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
workerScript.ramUsage = oneRamUsage;
// Add the WorkerScript to the global pool
workerScripts.set(pid, s);
workerScripts.set(pid, workerScript);
WorkerScriptStartStopEventEmitter.emit();
// Start the script's execution
let p: Promise<WorkerScript> | null = null; // Script's resulting promise
if (s.name.endsWith(".js") || s.name.endsWith(".ns")) {
p = startNetscript2Script(player, s);
let scriptExecution: Promise<void> | null = null; // Script's resulting promise
if (workerScript.name.endsWith(".js") || workerScript.name.endsWith(".ns")) {
scriptExecution = startNetscript2Script(player, workerScript);
} else {
p = startNetscript1Script(s);
if (!(p instanceof Promise)) {
scriptExecution = startNetscript1Script(workerScript);
if (!(scriptExecution instanceof Promise)) {
return false;
}
}
// Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false
p.then(function (w: WorkerScript) {
w.running = false;
w.env.stopFlag = true;
// On natural death, the earnings are transfered to the parent if it still exists.
if (parent !== undefined) {
if (parent.running) {
parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained;
parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade;
scriptExecution
.then(function () {
workerScript.running = false;
workerScript.env.stopFlag = true;
// On natural death, the earnings are transfered to the parent if it still exists.
if (parent !== undefined) {
if (parent.running) {
parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained;
parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade;
}
}
}
killWorkerScript(s);
w.log("", () => "Script finished running");
}).catch(function (w) {
if (w instanceof Error) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
return;
} else if (w instanceof WorkerScript) {
if (isScriptErrorMessage(w.errorMessage)) {
const errorTextArray = w.errorMessage.split("|DELIMITER|");
if (errorTextArray.length != 4) {
console.error("ERROR: Something wrong with Error text in evaluator...");
console.error("Error text: " + w.errorMessage);
return;
killWorkerScript(workerScript);
workerScript.log("", () => "Script finished running");
})
.catch(function (e) {
if (e instanceof Error) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + e.toString());
return;
} else if (e instanceof ScriptDeath) {
if (isScriptErrorMessage(workerScript.errorMessage)) {
const errorTextArray = workerScript.errorMessage.split("|DELIMITER|");
if (errorTextArray.length != 4) {
console.error("ERROR: Something wrong with Error text in evaluator...");
console.error("Error text: " + workerScript.errorMessage);
return;
}
const hostname = errorTextArray[1];
const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `RUNTIME ERROR<br>${scriptName}@${hostname} (PID - ${workerScript.pid})<br>`;
if (workerScript.args.length > 0) {
msg += `Args: ${arrayToString(workerScript.args)}<br>`;
}
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
workerScript.log("", () => "Script crashed with runtime error");
} else {
workerScript.log("", () => "Script killed");
return; // Already killed, so stop here
}
const hostname = errorTextArray[1];
const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `RUNTIME ERROR<br>${scriptName}@${hostname}<br>`;
if (w.args.length > 0) {
msg += `Args: ${arrayToString(w.args)}<br>`;
}
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
w.log("", () => "Script crashed with runtime error");
} else if (isScriptErrorMessage(e)) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error(
"ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " +
e.toString(),
);
return;
} else {
w.log("", () => "Script killed");
return; // Already killed, so stop here
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
console.error(e);
}
} else if (isScriptErrorMessage(w)) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error(
"ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " +
w.toString(),
);
return;
} else {
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
console.error(w);
}
killWorkerScript(s);
});
killWorkerScript(workerScript);
});
return true;
}
@@ -94,7 +94,7 @@ export const GraftingRoot = (): React.ReactElement => {
<Typography variant="h5">Graft Augmentations</Typography>
{getAvailableAugs(player).length > 0 ? (
<Paper sx={{ my: 1, width: "fit-content", display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<List sx={{ maxHeight: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}>
<List sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}>
{getAvailableAugs(player).map((k, i) => (
<ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
<Typography>{k}</Typography>
@@ -130,9 +130,13 @@ export const GraftingRoot = (): React.ReactElement => {
<>
Cancelling grafting will <b>not</b> save grafting progress, and the money you spend will <b>not</b>{" "}
be returned.
<br />
<br />
Additionally, grafting an Augmentation will increase the potency of the Entropy virus.
{!player.hasAugmentation(AugmentationNames.CongruityImplant) && (
<>
<br />
<br />
Additionally, grafting an Augmentation will increase the potency of the Entropy virus.
</>
)}
</>
}
/>
@@ -62,7 +62,7 @@ import { Money } from "../../ui/React/Money";
import React from "react";
import { serverMetadata } from "../../Server/data/servers";
import { SnackbarEvents } from "../../ui/React/Snackbar";
import { SnackbarEvents, ToastVariant } from "../../ui/React/Snackbar";
import { calculateClassEarnings } from "../formulas/work";
import { achievements } from "../../Achievements/Achievements";
import { FactionNames } from "../../Faction/data/FactionNames";
@@ -1366,15 +1366,19 @@ export function craftAugmentationWork(this: IPlayer, numCycles: number): boolean
export function finishGraftAugmentationWork(this: IPlayer, cancelled: boolean): string {
const augName = this.graftAugmentationName;
if (cancelled === false) {
dialogBoxCreate(
`You've finished crafting ${augName}.<br>The augmentation has been grafted to your body, but you feel a bit off.`,
);
applyAugmentation(Augmentations[augName]);
this.entropy += 1;
this.applyEntropy(this.entropy);
if (!this.hasAugmentation(AugmentationNames.CongruityImplant)) {
this.entropy += 1;
this.applyEntropy(this.entropy);
}
dialogBoxCreate(
`You've finished grafting ${augName}.<br>The augmentation has been applied to your body` +
(this.hasAugmentation(AugmentationNames.CongruityImplant) ? "." : ", but you feel a bit off."),
);
} else {
dialogBoxCreate(`You cancelled the crafting of ${augName}.<br>Your money was not returned to you.`);
dialogBoxCreate(`You cancelled the grafting of ${augName}.<br>Your money was not returned to you.`);
}
// Intelligence gain
@@ -1731,7 +1735,11 @@ export function regenerateHp(this: IPlayer, amt: number): void {
export function hospitalize(this: IPlayer): number {
const cost = getHospitalizationCost(this);
SnackbarEvents.emit(`You've been Hospitalized for ${numeralWrapper.formatMoney(cost)}`, "warning", 2000);
SnackbarEvents.emit(
`You've been Hospitalized for ${numeralWrapper.formatMoney(cost)}`,
ToastVariant.WARNING,
2000,
);
this.loseMoney(cost, "hospitalization");
this.hp = this.max_hp;
@@ -2706,7 +2714,7 @@ export function canAccessGrafting(this: IPlayer): boolean {
export function giveExploit(this: IPlayer, exploit: Exploit): void {
if (!this.exploits.includes(exploit)) {
this.exploits.push(exploit);
SnackbarEvents.emit("SF -1 acquired!", "success", 2000);
SnackbarEvents.emit("SF -1 acquired!", ToastVariant.SUCCESS, 2000);
}
}
@@ -2715,7 +2723,7 @@ export function giveAchievement(this: IPlayer, achievementId: string): void {
if (!achievement) return;
if (!this.achievements.map((a) => a.ID).includes(achievementId)) {
this.achievements.push({ ID: achievementId, unlockedOn: new Date().getTime() });
SnackbarEvents.emit(`Unlocked Achievement: "${achievement.Name}"`, "success", 2000);
SnackbarEvents.emit(`Unlocked Achievement: "${achievement.Name}"`, ToastVariant.SUCCESS, 2000);
}
}
-7
View File
@@ -11,12 +11,6 @@ import { SourceFiles } from "./SourceFile/SourceFiles";
import { dialogBoxCreate } from "./ui/React/DialogBox";
import { IRouter } from "./ui/Router";
export let redPillFlag = false;
export function setRedPillFlag(b: boolean): void {
redPillFlag = b;
}
function giveSourceFile(bitNodeNumber: number): void {
const sourceFileKey = "SourceFile" + bitNodeNumber.toString();
const sourceFile = SourceFiles[sourceFileKey];
@@ -82,7 +76,6 @@ export function enterBitNode(router: IRouter, flume: boolean, destroyedBitNode:
if (newBitNode === 5 && Player.intelligence === 0) {
Player.intelligence = 1;
}
redPillFlag = false;
// Set new Bit Node
Player.bitNodeN = newBitNode;
+2 -2
View File
@@ -11,7 +11,7 @@ import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
import { staneksGift, loadStaneksGift } from "./CotMG/Helper";
import { SnackbarEvents } from "./ui/React/Snackbar";
import { SnackbarEvents, ToastVariant } from "./ui/React/Snackbar";
import * as ExportBonus from "./ExportBonus";
@@ -114,7 +114,7 @@ class BitburnerSaveObject {
pushGameSaved(saveData);
if (emitToastEvent) {
SnackbarEvents.emit("Game Saved!", "info", 2000);
SnackbarEvents.emit("Game Saved!", ToastVariant.INFO, 2000);
}
return resolve();
})
+3
View File
@@ -230,6 +230,9 @@ async function parseOnlyRamCalculate(
} else if (ref in workerScript.env.vars.grafting) {
func = workerScript.env.vars.grafting[ref];
refDetail = `grafting.${ref}`;
} else if (ref in workerScript.env.vars.singularity) {
func = workerScript.env.vars.singularity[ref];
refDetail = `singularity.${ref}`;
} else {
func = workerScript.env.vars[ref];
refDetail = `${ref}`;
+86 -14
View File
@@ -101,24 +101,46 @@ interface Player {
/**
* @public
*/
interface RunningScript {
export interface RunningScript {
/** Arguments the script was called with */
args: string[];
/** Filename of the script */
filename: string;
/**
* Script logs as an array. The newest log entries are at the bottom.
* Timestamps, if enabled, are placed inside `[brackets]` at the start of each line.
**/
logs: string[];
/** Total amount of hacking experience earned from this script when offline */
offlineExpGained: number;
/** Total amount of money made by this script when offline */
offlineMoneyMade: number;
/** Offline running time of the script, in seconds **/
/** Number of seconds that the script has been running offline */
offlineRunningTime: number;
/** Total amount of hacking experience earned from this script when online */
onlineExpGained: number;
/** Total amount of money made by this script when online */
onlineMoneyMade: number;
/** Online running time of the script, in seconds **/
/** Number of seconds that this script has been running online */
onlineRunningTime: number;
/** Process ID. Must be an integer */
pid: number;
/** How much RAM this script uses for ONE thread */
ramUsage: number;
/** Hostname of the server on which this script runs */
server: string;
/** Number of threads that this script runs with */
threads: number;
}
/**
* @public
*/
export interface RecentScript extends RunningScript {
/** Timestamp of when the script was killed */
timeOfDeath: Date;
}
/**
* Data representing the internal values of a crime.
* @public
@@ -2618,7 +2640,7 @@ export interface Hacknet {
* // NS1:
* var upgradeName = "Sell for Corporation Funds";
* if (hacknet.numHashes() > hacknet.hashCost(upgradeName)) {
* hacknet.spendHashes(upgName);
* hacknet.spendHashes(upgradeName);
* }
* ```
* @example
@@ -2626,7 +2648,7 @@ export interface Hacknet {
* // NS2:
* const upgradeName = "Sell for Corporation Funds";
* if (ns.hacknet.numHashes() > ns.hacknet.hashCost(upgradeName)) {
* ns.hacknet.spendHashes(upgName);
* ns.hacknet.spendHashes(upgradeName);
* }
* ```
* @param upgName - Name of the upgrade of Hacknet Node.
@@ -4298,7 +4320,7 @@ interface UserInterface {
* {@link https://bitburner.readthedocs.io/en/latest/netscript/netscriptjs.html| ns2 in-game docs}
* <hr>
*/
export interface NS extends Singularity {
export interface NS {
/**
* Namespace for hacknet functions.
* @remarks RAM cost: 4 GB
@@ -4555,9 +4577,10 @@ export interface NS extends Singularity {
* Returns the security increase that would occur if a hack with this many threads happened.
*
* @param threads - Amount of threads that will be used.
* @param hostname - Hostname of the target server. The number of threads is limited to the number needed to hack the servers maximum amount of money.
* @returns The security increase.
*/
hackAnalyzeSecurity(threads: number): number;
hackAnalyzeSecurity(threads: number, hostname?: string): number;
/**
* Get the chance of successfully hacking a server.
@@ -4781,6 +4804,27 @@ export interface NS extends Singularity {
*/
getScriptLogs(fn?: string, host?: string, ...args: any[]): string[];
/**
* Get an array of recently killed scripts across all servers.
* @remarks
* RAM cost: 0.2 GB
*
* The most recently killed script is the first element in the array.
* Note that there is a maximum number of recently killed scripts which are tracked.
* This is configurable in the game's options as `Recently killed scripts size`.
*
* @example
* ```ts
* let recentScripts = ns.getRecentScripts();
* let mostRecent = recentScripts.shift()
* if (mostRecent)
* ns.tprint(mostRecent.logs.join('\n'))
* ```
*
* @returns Array with information about previously killed scripts.
*/
getRecentScripts(): RecentScript[];
/**
* Open the tail window of a script.
* @remarks
@@ -6183,7 +6227,7 @@ export interface NS extends Singularity {
* @param variant - Type of toast, must be one of success, info, warning, error. Defaults to success.
* @param duration - Duration of toast in ms. Can also be `null` to create a persistent toast. Defaults to 2000
*/
toast(msg: any, variant?: string, duration?: number | null): void;
toast(msg: any, variant?: ToastVariantValues, duration?: number | null): void;
/**
* Download a file from the internet.
@@ -6378,6 +6422,24 @@ export interface NS extends Singularity {
* RAM cost: 0.2 GB
*/
getSharePower(): number;
enums: NSEnums;
}
/** @public */
export enum ToastVariant {
SUCCESS = "success",
WARNING = "warning",
ERROR = "error",
INFO = "info",
}
/** @public */
export type ToastVariantValues = `${ToastVariant}`;
/** @public */
export interface NSEnums {
toast: typeof ToastVariant;
}
/**
@@ -6914,10 +6976,14 @@ interface Employee {
interface Product {
/** Name of the product */
name: string;
/** Demand for the product */
dmd: number;
/** Competition for the product */
cmp: number;
/** Demand for the product, only present if "Market Research - Demand" unlocked */
dmd: number | undefined;
/** Competition for the product, only present if "Market Research - Competition" unlocked */
cmp: number | undefined;
/** Product Rating */
rat: number;
/** Product Properties. The data is \{qlt, per, dur, rel, aes, fea\} */
properties: { [key: string]: number };
/** Production cost */
pCost: number;
/** Sell cost, can be "MP+5" */
@@ -6941,7 +7007,11 @@ interface Material {
qty: number;
/** Quality of the material */
qlt: number;
/** Amount of material produced */
/** Demand for the material, only present if "Market Research - Demand" unlocked */
dmd: number | undefined;
/** Competition for the material, only present if "Market Research - Competition" unlocked */
cmp: number | undefined;
/** Amount of material produced */
prod: number;
/** Amount of material sold */
sell: number;
@@ -6987,8 +7057,10 @@ interface Office {
maxMor: number;
/** Name of all the employees */
employees: string[];
/** Positions of the employees */
/** Production of the employees */
employeeProd: EmployeeJobs;
/** Positions of the employees */
employeeJobs: EmployeeJobs;
}
/**
+3 -3
View File
@@ -981,11 +981,11 @@ export function Root(props: IProps): React.ReactElement {
</Button>
<Typography>
{" "}
Documentation:{" "}
<strong>Documentation:</strong>{" "}
<Link target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html">
Basic
</Link>{" "}
|
</Link>
{" | "}
<Link target="_blank" href="https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.ns.md">
Full
</Link>
+13
View File
@@ -248,6 +248,19 @@ export class BaseServer {
this.ramUsed = ram;
}
pushProgram(program: string): void {
if (this.programs.includes(program)) return;
// Remove partially created program if there is one
const existingPartialExeIndex = this.programs.findIndex((p) => p.startsWith(program));
// findIndex returns -1 if there is no match, we only want to splice on a match
if (existingPartialExeIndex > -1) {
this.programs.splice(existingPartialExeIndex, 1);
}
this.programs.push(program);
}
/**
* Write to a script file
* Overwrites existing files. Creates new files if the script does not eixst
+7
View File
@@ -63,6 +63,11 @@ interface IDefaultSettings {
*/
Locale: string;
/**
* Limit the number of recently killed script entries being tracked.
*/
MaxRecentScriptsCapacity: number;
/**
* Limit the number of log entries for each script being executed on each server.
*/
@@ -191,6 +196,7 @@ export const defaultSettings: IDefaultSettings = {
EnableBashHotkeys: false,
TimestampsFormat: "",
Locale: "en",
MaxRecentScriptsCapacity: 50,
MaxLogCapacity: 50,
MaxPortCapacity: 50,
MaxTerminalCapacity: 500,
@@ -228,6 +234,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
EnableBashHotkeys: defaultSettings.EnableBashHotkeys,
TimestampsFormat: defaultSettings.TimestampsFormat,
Locale: "en",
MaxRecentScriptsCapacity: defaultSettings.MaxRecentScriptsCapacity,
MaxLogCapacity: defaultSettings.MaxLogCapacity,
MaxPortCapacity: defaultSettings.MaxPortCapacity,
MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity,
+1 -2
View File
@@ -51,7 +51,6 @@ import { CONSTANTS } from "../../Constants";
import { iTutorialSteps, iTutorialNextStep, ITutorial } from "../../InteractiveTutorial";
import { getAvailableCreatePrograms } from "../../Programs/ProgramHelpers";
import { Settings } from "../../Settings/Settings";
import { redPillFlag } from "../../RedPill";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { ProgramsSeen } from "../../Programs/ui/ProgramsRoot";
@@ -275,7 +274,7 @@ export function SidebarRoot(props: IProps): React.ReactElement {
// Alt-o - Options
function handleShortcuts(this: Document, event: KeyboardEvent): any {
if (Settings.DisableHotkeys) return;
if ((props.player.isWorking && props.player.focus) || redPillFlag) return;
if ((props.player.isWorking && props.player.focus) || props.router.page() === Page.BitVerse) return;
if (event.key === KEY.T && event.altKey) {
event.preventDefault();
clickTerminal();
+1 -1
View File
@@ -306,7 +306,7 @@ export function processStockPrices(numCycles = 1): void {
}
}
export function initStockMarketFnForReact(): void {
export function initStockMarketFn(): void {
initStockMarket();
initSymbolToStockMap();
}
+2 -2
View File
@@ -191,8 +191,8 @@ export const HelpTexts: IMap<string[]> = {
"Usage: connect [hostname]",
" ",
"Connect to a remote server. The hostname of the remote server must be given as the argument ",
"to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To ",
"see which servers can be connected to, use the 'scan' command.",
"to this command. Note that only servers that are immediately adjacent to the current server in the network and the ones that have",
"a backdoor installed can be connected to. To see which servers can be connected to, use the 'scan' command.",
" ",
],
cp: ["Usage: cp [src] [dst]", " ", "Copy a file on this server. To copy a file to another server use scp.", " "],
@@ -4,7 +4,8 @@ import { getSubdirectories } from "./DirectoryServerHelpers";
import { Aliases, GlobalAliases, substituteAliases } from "../Alias";
import { DarkWebItems } from "../DarkWeb/DarkWebItems";
import { IPlayer } from "../PersonObjects/IPlayer";
import { GetServer, GetAllServers } from "../Server/AllServers";
import { GetAllServers } from "../Server/AllServers";
import { Server } from "../Server/Server";
import { ParseCommand, ParseCommands } from "./Parser";
import { HelpTexts } from "./HelpText";
import { isScriptFilename } from "../Script/isScriptFilename";
@@ -238,16 +239,14 @@ export async function determineAllPossibilitiesForTabCompletion(
}
if (isCommand("connect")) {
// All network connections
for (let i = 0; i < currServ.serversOnNetwork.length; ++i) {
const serv = GetServer(currServ.serversOnNetwork[i]);
if (serv == null) {
continue;
}
allPos.push(serv.hostname);
}
return allPos;
// All directly connected and backdoored servers are reachable
console.log(GetAllServers());
return GetAllServers()
.filter(
(server) =>
currServ.serversOnNetwork.includes(server.hostname) || (server instanceof Server && server.backdoorInstalled),
)
.map((server) => server.hostname);
}
if (isCommand("nano") || isCommand("vim")) {
+2 -2
View File
@@ -11,7 +11,7 @@ import { StyleEditorButton } from "./StyleEditorButton";
import { ThemeEntry } from "./ThemeEntry";
import { ThemeCollaborate } from "./ThemeCollaborate";
import { Modal } from "../../ui/React/Modal";
import { SnackbarEvents } from "../../ui/React/Snackbar";
import { SnackbarEvents, ToastVariant } from "../../ui/React/Snackbar";
interface IProps {
router: IRouter;
@@ -54,7 +54,7 @@ export function ThemeBrowser({ router }: IProps): React.ReactElement {
UNDO
</Button>
</>,
"info",
ToastVariant.INFO,
30000,
);
}
+3 -3
View File
@@ -1,4 +1,4 @@
import { WorkerScript } from "./Netscript/WorkerScript";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { isScriptErrorMessage } from "./NetscriptEvaluator";
import { dialogBoxCreate } from "./ui/React/DialogBox";
@@ -14,9 +14,9 @@ export function setupUncaughtPromiseHandler(): void {
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
} else if (e.reason instanceof WorkerScript) {
} else if (e.reason instanceof ScriptDeath) {
const msg =
`UNCAUGHT PROMISE ERROR<br>You forgot to await a promise<br>${e.reason.name}@${e.reason.hostname}<br>` +
`UNCAUGHT PROMISE ERROR<br>You forgot to await a promise<br>${e.reason.name}@${e.reason.hostname} (PID - ${e.reason.pid})<br>` +
`Maybe hack / grow / weaken ?`;
dialogBoxCreate(msg);
}
+296 -1
View File
@@ -1,5 +1,7 @@
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { MinHeap } from "../utils/Heap";
import { HammingEncode, HammingDecode } from "../utils/HammingCodeTools";
/* tslint:disable:completed-docs no-magic-numbers arrow-return-shorthand */
/* Function that generates a valid 'data' for a contract type */
@@ -122,7 +124,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"&nbsp;&nbsp;&nbsp;&nbsp;2 + 2\n",
"&nbsp;&nbsp;&nbsp;&nbsp;2 + 1 + 1\n",
"&nbsp;&nbsp;&nbsp;&nbsp;1 + 1 + 1 + 1\n\n",
`How many different ways can the number ${n} be written as a sum of at least`,
`How many different distinct ways can the number ${n} be written as a sum of at least`,
"two positive integers?",
].join(" ");
},
@@ -145,6 +147,51 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return ways[data] === parseInt(ans, 10);
},
},
{
desc: (data: [number, number[]]): string => {
const n: number = data[0];
const s: number[] = data[1];
return [
`How many different distinct ways can the number ${n} be written`,
"as a sum of integers contained in the set:\n\n",
`[${s}]?\n\n`,
"You may use each integer in the set zero or more times.",
].join(" ");
},
difficulty: 2,
gen: (): [number, number[]] => {
const n: number = getRandomInt(12, 200);
const maxLen: number = getRandomInt(8, 12);
const s: number[] = [];
// Bias towards small numbers is intentional to have much bigger answers in general
// to force people better optimize their solutions
for (let i = 1; i <= n; i++) {
if (s.length == maxLen) {
break;
}
if (Math.random() < 0.6 || n - i < maxLen - s.length) {
s.push(i);
}
}
return [n, s];
},
name: "Total Ways to Sum II",
numTries: 10,
solver: (data: [number, number[]], ans: string): boolean => {
// https://www.geeksforgeeks.org/coin-change-dp-7/?ref=lbp
const n = data[0];
const s = data[1];
const ways: number[] = [1];
ways.length = n + 1;
ways.fill(0, 1);
for (let i = 0; i < s.length; i++) {
for (let j = s[i]; j <= n; j++) {
ways[j] += ways[j - s[i]];
}
}
return ways[n] === parseInt(ans, 10);
},
},
{
desc: (n: number[][]): string => {
let d: string = [
@@ -310,6 +357,62 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return (ans === "1" && solution) || (ans === "0" && !solution);
},
},
{
desc: (arr: number[]): string => {
return [
"You are given the following array of integers:\n\n",
`${arr}\n\n`,
"Each element in the array represents your MAXIMUM jump length",
"at that position. This means that if you are at position i and your",
"maximum jump length is n, you can jump to any position from",
"i to i+n.",
"\n\nAssuming you are initially positioned",
"at the start of the array, determine the minimum number of",
"jumps to reach the end of the array.\n\n",
"If it's impossible to reach the end, then the answer should be 0.",
].join(" ");
},
difficulty: 3,
gen: (): number[] => {
const len: number = getRandomInt(3, 25);
const arr: number[] = [];
arr.length = len;
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < 10; j++) {
if (Math.random() <= j / 10 + 0.1) {
arr[i] = j;
break;
}
}
}
return arr;
},
name: "Array Jumping Game II",
numTries: 3,
solver: (data: number[], ans: string): boolean => {
const n: number = data.length;
let reach = 0;
let jumps = 0;
let lastJump = -1;
while (reach < n - 1) {
let jumpedFrom = -1;
for (let i = reach; i > lastJump; i--) {
if (i + data[i] > reach) {
reach = i + data[i];
jumpedFrom = i;
}
}
if (jumpedFrom === -1) {
jumps = 0;
break;
}
lastJump = jumpedFrom;
jumps++;
}
return jumps === parseInt(ans, 10);
},
},
{
desc: (arr: number[][]): string => {
return [
@@ -794,6 +897,140 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return obstacleGrid[obstacleGrid.length - 1][obstacleGrid[0].length - 1] === parseInt(ans);
},
},
{
name: "Shortest Path in a Grid",
desc: (data: number[][]): string => {
return [
"You are located in the top-left corner of the following grid:\n\n",
`&nbsp;&nbsp;[${data.map((line) => "[" + line + "]").join(",\n&nbsp;&nbsp;&nbsp;")}]\n\n`,
"You are trying to find the shortest path to the bottom-right corner of the grid,",
"but there are obstacles on the grid that you cannot move onto.",
"These obstacles are denoted by '1', while empty spaces are denoted by 0.\n\n",
"Determine the shortest path from start to finish, if one exists.",
"The answer should be given as a string of UDLR characters, indicating the moves along the path\n\n",
"NOTE: If there are multiple equally short paths, any of them is accepted as answer.",
"If there is no path, the answer should be an empty string.\n",
"NOTE: The data returned for this contract is an 2D array of numbers representing the grid.\n\n",
"Examples:\n\n",
"&nbsp;&nbsp;&nbsp;&nbsp;[[0,1,0,0,0],\n",
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[0,0,0,1,0]]\n",
"\n",
"Answer: 'DRRURRD'\n\n",
"&nbsp;&nbsp;&nbsp;&nbsp;[[0,1],\n",
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[1,0]]\n",
"\n",
"Answer: ''\n\n",
].join(" ");
},
difficulty: 7,
numTries: 10,
gen: (): number[][] => {
const height = getRandomInt(6, 12);
const width = getRandomInt(6, 12);
const dstY = height - 1;
const dstX = width - 1;
const minPathLength = dstY + dstX; // Math.abs(dstY - srcY) + Math.abs(dstX - srcX)
const grid: number[][] = new Array(height);
for (let y = 0; y < height; y++) grid[y] = new Array(width).fill(0);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (y == 0 && x == 0) continue; // Don't block start
if (y == dstY && x == dstX) continue; // Don't block destination
// Generate more obstacles the farther a position is from start and destination.
// Raw distance factor peaks at 50% at half-way mark. Rescale to 40% max.
// Obstacle chance range of [15%, 40%] produces ~78% solvable puzzles
const distanceFactor = (Math.min(y + x, dstY - y + dstX - x) / minPathLength) * 0.8;
if (Math.random() < Math.max(0.15, distanceFactor)) grid[y][x] = 1;
}
}
return grid;
},
solver: (data: number[][], ans: string): boolean => {
const width = data[0].length;
const height = data.length;
const dstY = height - 1;
const dstX = width - 1;
const distance: [number][] = new Array(height);
//const prev: [[number, number] | undefined][] = new Array(height);
const queue = new MinHeap<[number, number]>();
for (let y = 0; y < height; y++) {
distance[y] = new Array(width).fill(Infinity) as [number];
//prev[y] = new Array(width).fill(undefined) as [undefined];
}
function validPosition(y: number, x: number): boolean {
return y >= 0 && y < height && x >= 0 && x < width && data[y][x] == 0;
}
// List in-bounds and passable neighbors
function* neighbors(y: number, x: number): Generator<[number, number]> {
if (validPosition(y - 1, x)) yield [y - 1, x]; // Up
if (validPosition(y + 1, x)) yield [y + 1, x]; // Down
if (validPosition(y, x - 1)) yield [y, x - 1]; // Left
if (validPosition(y, x + 1)) yield [y, x + 1]; // Right
}
// Prepare starting point
distance[0][0] = 0;
queue.push([0, 0], 0);
// Take next-nearest position and expand potential paths from there
while (queue.size > 0) {
const [y, x] = queue.pop() as [number, number];
for (const [yN, xN] of neighbors(y, x)) {
const d = distance[y][x] + 1;
if (d < distance[yN][xN]) {
if (distance[yN][xN] == Infinity)
// Not reached previously
queue.push([yN, xN], d);
// Found a shorter path
else queue.changeWeight(([yQ, xQ]) => yQ == yN && xQ == xN, d);
//prev[yN][xN] = [y, x];
distance[yN][xN] = d;
}
}
}
// No path at all?
if (distance[dstY][dstX] == Infinity) return ans == "";
// There is a solution, require that the answer path is as short as the shortest
// path we found
if (ans.length > distance[dstY][dstX]) return false;
// Further verify that the answer path is a valid path
let ansX = 0;
let ansY = 0;
for (const direction of ans) {
switch (direction) {
case "U":
ansY -= 1;
break;
case "D":
ansY += 1;
break;
case "L":
ansX -= 1;
break;
case "R":
ansX += 1;
break;
default:
return false; // Invalid character
}
if (!validPosition(ansY, ansX)) return false;
}
// Path was valid, finally verify that the answer path brought us to the end coordinates
return ansY == dstY && ansX == dstX;
},
},
{
desc: (data: string): string => {
return [
@@ -1008,4 +1245,62 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return true;
},
},
{
name: "HammingCodes: Integer to encoded Binary",
numTries: 10,
difficulty: 5,
desc: (n: number): string => {
return [
"You are given the following decimal Value: \n",
`${n} \n`,
"Convert it into a binary string and encode it as a 'Hamming-Code'. eg:\n ",
"Value 8 will result into binary '1000', which will be encoded",
"with the pattern 'pppdpddd', where p is a paritybit and d a databit,\n",
"or '10101' (Value 21) will result into (pppdpdddpd) '1001101011'.\n\n",
"NOTE: You need an parity Bit on Index 0 as an 'overall'-paritybit. \n",
"NOTE 2: You should watch the HammingCode-video from 3Blue1Brown, which explains the 'rule' of encoding,",
"including the first Index parity-bit mentioned on the first note.\n\n",
"Now the only one rule for this encoding:\n",
" It's not allowed to add additional leading '0's to the binary value\n",
"That means, the binary value has to be encoded as it is",
].join(" ");
},
gen: (): number => {
return getRandomInt(Math.pow(2, 4), Math.pow(2, getRandomInt(1, 57)));
},
solver: (data: number, ans: string): boolean => {
return ans === HammingEncode(data);
},
},
{
name: "HammingCodes: Encoded Binary to Integer",
difficulty: 8,
numTries: 10,
desc: (n: string): string => {
return [
"You are given the following encoded binary String: \n",
`'${n}' \n`,
"Treat it as a Hammingcode with 1 'possible' error on an random Index.\n",
"Find the 'possible' wrong bit, fix it and extract the decimal value, which is hidden inside the string.\n\n",
"Note: The length of the binary string is dynamic, but it's encoding/decoding is following Hammings 'rule'\n",
"Note 2: Index 0 is an 'overall' parity bit. Watch the Hammingcode-video from 3Blue1Brown for more information\n",
"Note 3: There's a ~55% chance for an altered Bit. So... MAYBE there is an altered Bit 😉\n",
"Extranote for automation: return the decimal value as a string",
].join(" ");
},
gen: (): string => {
const _alteredBit = Math.round(Math.random());
const _buildArray: Array<string> = HammingEncode(
getRandomInt(Math.pow(2, 4), Math.pow(2, getRandomInt(1, 57))),
).split("");
if (_alteredBit) {
const _randomIndex: number = getRandomInt(0, _buildArray.length - 1);
_buildArray[_randomIndex] = _buildArray[_randomIndex] == "0" ? "1" : "0";
}
return _buildArray.join("");
},
solver: (data: string, ans: string): boolean => {
return parseInt(ans, 10) === HammingDecode(data);
},
},
];
+2 -2
View File
@@ -50,7 +50,7 @@ import { calculateAchievements } from "./Achievements/Achievements";
import React from "react";
import { setupUncaughtPromiseHandler } from "./UncaughtPromiseHandler";
import { Button, Typography } from "@mui/material";
import { SnackbarEvents } from "./ui/React/Snackbar";
import { SnackbarEvents, ToastVariant } from "./ui/React/Snackbar";
const Engine: {
_lastUpdate: number;
@@ -495,7 +495,7 @@ function warnAutosaveDisabled(): void {
</Button>
</>
);
SnackbarEvents.emit(warningToast, "warning", 5000);
SnackbarEvents.emit(warningToast, ToastVariant.WARNING, 5000);
}
export { Engine };
@@ -57,8 +57,8 @@ export function RecentScriptAccordion(props: IProps): React.ReactElement {
<ListItemText
primary={
<Typography>
{recentScript.filename} (died{" "}
{convertTimeMsToTimeElapsedString(new Date().getTime() - recentScript.timestamp.getTime())} ago)
{recentScript.runningScript.filename} (died{" "}
{convertTimeMsToTimeElapsedString(new Date().getTime() - recentScript.timeOfDeath.getTime())} ago)
</Typography>
}
/>
@@ -78,7 +78,7 @@ export function RecentScriptAccordion(props: IProps): React.ReactElement {
</TableRow>
<TableRow>
<TableCell className={classes.noborder} colSpan={2}>
<Typography> Args: {arrayToString(recentScript.args)}</Typography>
<Typography> Args: {arrayToString(recentScript.runningScript.args)}</Typography>
</TableCell>
</TableRow>
<TableRow>
+1 -1
View File
@@ -13,7 +13,7 @@ export function RecentScriptsPage(): React.ReactElement {
<>
<Typography>List of all recently killed scripts.</Typography>
{recentScripts.map((r) => (
<RecentScriptAccordion key={r.pid} recentScript={r} />
<RecentScriptAccordion key={r.runningScript.pid} recentScript={r} />
))}
</>
);
+361 -344
View File
@@ -1,144 +1,80 @@
import React, { useState, useEffect } from "react";
import { numeralWrapper } from "./numeralFormat";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { getPurchaseServerLimit } from "../Server/ServerPurchases";
import { HacknetServerConstants } from "../Hacknet/data/Constants";
import { StatsTable } from "./React/StatsTable";
import { Money } from "./React/Money";
import { use } from "./Context";
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
import { Paper, Table, TableBody, Box, IconButton, Typography, Container, Tooltip } from "@mui/material";
import { MoreHoriz, Info } from "@mui/icons-material";
import React, { useEffect, useState } from "react";
import { BitNodes } from "../BitNode/BitNode";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { HacknetServerConstants } from "../Hacknet/data/Constants";
import { getPurchaseServerLimit } from "../Server/ServerPurchases";
import { Settings } from "../Settings/Settings";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { use } from "./Context";
import { numeralWrapper } from "./numeralFormat";
import { Modal } from "./React/Modal";
import { Money } from "./React/Money";
import { StatsRow } from "./React/StatsRow";
import { StatsTable } from "./React/StatsTable";
import TableBody from "@mui/material/TableBody";
import { Table, TableCell } from "./React/Table";
import TableRow from "@mui/material/TableRow";
function LastEmployer(): React.ReactElement {
const player = use.Player();
if (player.companyName) {
return <Typography>Employer at which you last worked: {player.companyName}</Typography>;
}
return <></>;
interface EmployersModalProps {
open: boolean;
onClose: () => void;
}
function LastJob(): React.ReactElement {
const EmployersModal = ({ open, onClose }: EmployersModalProps): React.ReactElement => {
const player = use.Player();
if (player.companyName !== "") {
return <Typography>Job you last worked: {player.jobs[player.companyName]}</Typography>;
}
return <></>;
}
function Employers(): React.ReactElement {
const player = use.Player();
if (player.jobs && Object.keys(player.jobs).length !== 0)
return (
return (
<Modal open={open} onClose={onClose}>
<>
<Typography>All Employers:</Typography>
<Typography variant="h5">All Employers</Typography>
<ul>
{Object.keys(player.jobs).map((j) => (
<Typography key={j}> * {j}</Typography>
<Typography key={j}>* {j}</Typography>
))}
</ul>
</>
);
return <></>;
}
function Hacknet(): React.ReactElement {
const player = use.Player();
// Can't import HacknetHelpers for some reason.
if (!(player.bitNodeN === 9 || SourceFileFlags[9] > 0)) {
return (
<>
<Typography>{`Hacknet Nodes owned: ${player.hacknetNodes.length}`}</Typography>
<br />
</>
);
} else {
return (
<>
<Typography>{`Hacknet Servers owned: ${player.hacknetNodes.length} / ${HacknetServerConstants.MaxServers}`}</Typography>
<br />
</>
);
}
}
function Intelligence(): React.ReactElement {
const player = use.Player();
if (player.intelligence > 0 && (player.bitNodeN === 5 || SourceFileFlags[5] > 0)) {
return (
<TableRow>
<TableCell>
<Typography>Intelligence:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography>{numeralWrapper.formatSkill(player.intelligence)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.intelligence_exp)} exp)</Typography>
</TableCell>
</TableRow>
);
}
return <></>;
}
function MultiplierTable(props: any): React.ReactElement {
function bn5Stat(r: any): JSX.Element {
if (SourceFileFlags[5] > 0 && r.length > 2 && r[1] != r[2]) {
return (
<TableCell key="2" align="right">
<Typography noWrap>({numeralWrapper.formatPercentage(r[2])})</Typography>
</TableCell>
);
}
return <></>;
}
return (
<>
<Table size="small" padding="none">
<TableBody>
{props.rows.map((r: any) => (
<TableRow key={r[0]}>
<TableCell key="0">
<Typography noWrap>{`${r[0]} multiplier:`}&nbsp;</Typography>
</TableCell>
<TableCell key="1" align="right">
<Typography noWrap>{numeralWrapper.formatPercentage(r[1])}</Typography>
</TableCell>
{bn5Stat(r)}
</TableRow>
))}
</TableBody>
</Table>
</>
</Modal>
);
};
interface MultTableProps {
rows: (string | number)[][];
color: string;
noMargin?: boolean;
}
function BladeburnerMults(): React.ReactElement {
const player = use.Player();
if (!player.canAccessBladeburner()) return <></>;
function MultiplierTable(props: MultTableProps): React.ReactElement {
return (
<MultiplierTable
rows={[
["Bladeburner Success Chance", player.bladeburner_success_chance_mult],
["Bladeburner Max Stamina", player.bladeburner_max_stamina_mult],
["Bladeburner Stamina Gain", player.bladeburner_stamina_gain_mult],
["Bladeburner Field Analysis", player.bladeburner_analysis_mult],
]}
/>
<Table sx={{ display: "table", width: "100%", mb: (props.noMargin ?? false) === true ? 0 : 2 }}>
<TableBody>
{props.rows.map((data) => {
const mult = data[0] as string,
value = data[1] as number,
modded = data[2] as number | null;
if (modded && modded !== value && SourceFileFlags[5] > 0) {
return (
<StatsRow key={mult} name={mult} color={props.color} data={{}}>
<>
<Typography color={props.color}>
<span style={{ opacity: 0.5 }}>{numeralWrapper.formatPercentage(value)}</span>{" "}
{numeralWrapper.formatPercentage(modded)}
</Typography>
</>
</StatsRow>
);
}
return (
<StatsRow
key={mult}
name={mult}
color={props.color}
data={{ content: numeralWrapper.formatPercentage(value) }}
/>
);
})}
</TableBody>
</Table>
);
}
@@ -146,15 +82,17 @@ function CurrentBitNode(): React.ReactElement {
const player = use.Player();
if (player.sourceFiles.length > 0) {
const index = "BitNode" + player.bitNodeN;
const currentSourceFile = player.sourceFiles.find((sourceFile) => sourceFile.n == player.bitNodeN);
const lvl = currentSourceFile ? currentSourceFile.lvl : 0;
return (
<>
<Typography variant="h4">
BitNode {player.bitNodeN}: {BitNodes[index].name}
</Typography>
<Typography sx={{ mx: 2 }} style={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>
{BitNodes[index].info}
</Typography>
</>
<Box>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">
BitNode {player.bitNodeN}: {BitNodes[index].name} (Level {lvl})
</Typography>
<Typography sx={{ whiteSpace: "pre-wrap", overflowWrap: "break-word" }}>{BitNodes[index].info}</Typography>
</Paper>
</Box>
);
}
@@ -262,6 +200,7 @@ function MoneyModal({ open, onClose }: IMoneyModalProps): React.ReactElement {
export function CharacterStats(): React.ReactElement {
const player = use.Player();
const [moneyOpen, setMoneyOpen] = useState(false);
const [employersOpen, setEmployersOpen] = useState(false);
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
@@ -273,229 +212,307 @@ export function CharacterStats(): React.ReactElement {
}, []);
const timeRows = [
["Time played since last Augmentation:", convertTimeMsToTimeElapsedString(player.playtimeSinceLastAug)],
["Since last Augmentation installation", convertTimeMsToTimeElapsedString(player.playtimeSinceLastAug)],
];
if (player.sourceFiles.length > 0) {
timeRows.push([
"Time played since last Bitnode destroyed:",
convertTimeMsToTimeElapsedString(player.playtimeSinceLastBitnode),
]);
timeRows.push(["Since last Bitnode destroyed", convertTimeMsToTimeElapsedString(player.playtimeSinceLastBitnode)]);
}
timeRows.push(["Total Time played:", convertTimeMsToTimeElapsedString(player.totalPlaytime)]);
timeRows.push(["Total", convertTimeMsToTimeElapsedString(player.totalPlaytime)]);
return (
<>
<Typography variant="h4">General</Typography>
<Box sx={{ mx: 2 }}>
<Typography>Current City: {player.city}</Typography>
<LastEmployer />
<LastJob />
<Employers />
<Typography>
Money: <Money money={player.money} />
<IconButton onClick={() => setMoneyOpen(true)}>
<MoreHorizIcon color="info" />
</IconButton>
</Typography>
</Box>
<br />
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">Stats</Typography>
<Box sx={{ mx: 2 }}>
<Table size="small" padding="none">
<TableBody>
<TableRow>
<TableCell>
<Typography noWrap>Hacking:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.hacking)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.hacking_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Strength:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.strength)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.strength_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Defense:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.defense)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.defense_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Dexterity:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.dexterity)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.dexterity_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Agility:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.agility)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.agility_exp)} exp)</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Charisma:&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>{numeralWrapper.formatSkill(player.charisma)}&nbsp;</Typography>
</TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.charisma_exp)} exp)</Typography>
</TableCell>
</TableRow>
<Intelligence />
</TableBody>
</Table>
<br />
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", minWidth: "fit-content", mb: 1, gap: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">General</Typography>
<Table>
<TableBody>
<StatsRow name="Current City" color={Settings.theme.primary} data={{ content: player.city }} />
<StatsRow name="Money" color={Settings.theme.money} data={{}}>
<>
<Money money={player.money} />
<IconButton onClick={() => setMoneyOpen(true)} sx={{ p: 0 }}>
<MoreHoriz color="info" />
</IconButton>
</>
</StatsRow>
{player.companyName ? (
<>
<StatsRow
name="Last Employer"
color={Settings.theme.primary}
data={{ content: player.companyName }}
/>
<StatsRow
name="Last Job"
color={Settings.theme.primary}
data={{ content: player.jobs[player.companyName] }}
/>
</>
) : (
<></>
)}
{player.jobs && Object.keys(player.jobs).length !== 0 ? (
<StatsRow name="All Employers" color={Settings.theme.primary} data={{}}>
<>
<span style={{ color: Settings.theme.primary }}>{Object.keys(player.jobs).length} total</span>
<IconButton onClick={() => setEmployersOpen(true)} sx={{ p: 0 }}>
<MoreHoriz color="info" />
</IconButton>
</>
</StatsRow>
) : (
<></>
)}
<StatsRow
name="Servers Owned"
color={Settings.theme.primary}
data={{ content: `${player.purchasedServers.length} / ${getPurchaseServerLimit()}` }}
/>
<StatsRow
name={`Hacknet ${player.bitNodeN === 9 || SourceFileFlags[9] > 0 ? "Servers" : "Nodes"} owned`}
color={Settings.theme.primary}
data={{
content: `${player.hacknetNodes.length}${
player.bitNodeN === 9 || SourceFileFlags[9] > 0 ? ` / ${HacknetServerConstants.MaxServers}` : ""
}`,
}}
/>
<StatsRow
name="Augmentations Installed"
color={Settings.theme.primary}
data={{ content: String(player.augmentations.length) }}
/>
</TableBody>
</Table>
</Paper>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Skills</Typography>
<Table>
<TableBody>
<StatsRow
name="Hacking"
color={Settings.theme.hack}
data={{ level: player.hacking, exp: player.hacking_exp }}
/>
<StatsRow
name="Strength"
color={Settings.theme.combat}
data={{ level: player.strength, exp: player.strength_exp }}
/>
<StatsRow
name="Defense"
color={Settings.theme.combat}
data={{ level: player.defense, exp: player.defense_exp }}
/>
<StatsRow
name="Dexterity"
color={Settings.theme.combat}
data={{ level: player.dexterity, exp: player.dexterity_exp }}
/>
<StatsRow
name="Agility"
color={Settings.theme.combat}
data={{ level: player.agility, exp: player.agility_exp }}
/>
<StatsRow
name="Charisma"
color={Settings.theme.cha}
data={{ level: player.charisma, exp: player.charisma_exp }}
/>
{player.intelligence > 0 && (player.bitNodeN === 5 || SourceFileFlags[5] > 0) && (
<StatsRow
name="Intelligence"
color={Settings.theme.int}
data={{ level: player.intelligence, exp: player.intelligence_exp }}
/>
)}
</TableBody>
</Table>
</Paper>
</Box>
<br />
<Typography variant="h4">Multipliers</Typography>
<Box sx={{ mx: 2 }}>
<MultiplierTable
rows={[
["Hacking Chance", player.hacking_chance_mult],
["Hacking Speed", player.hacking_speed_mult],
[
"Hacking Money",
player.hacking_money_mult,
player.hacking_money_mult * BitNodeMultipliers.ScriptHackMoney,
],
[
"Hacking Growth",
player.hacking_grow_mult,
player.hacking_grow_mult * BitNodeMultipliers.ServerGrowthRate,
],
]}
/>
<br />
<MultiplierTable
rows={[
["Hacking Level", player.hacking_mult, player.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier],
["Hacking Experience", player.hacking_exp_mult, player.hacking_exp_mult * BitNodeMultipliers.HackExpGain],
]}
/>
<br />
<Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5" color="primary" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
Multipliers
{SourceFileFlags[5] > 0 && (
<Tooltip
title={
<Typography>
Displays your current multipliers.
<br />
<br />
When there is a dim number next to a multiplier, that means that the multiplier in question is being
affected by BitNode multipliers.
<br />
<br />
The dim number is the raw multiplier, and the undimmed number is the effective multiplier, as
dictated by the BitNode.
</Typography>
}
>
<Info sx={{ ml: 1, mb: 0.5 }} color="info" />
</Tooltip>
)}
</Typography>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1 }}>
<Box>
<MultiplierTable
rows={[
["Hacking Chance", player.hacking_chance_mult],
["Hacking Speed", player.hacking_speed_mult],
[
"Hacking Money",
player.hacking_money_mult,
player.hacking_money_mult * BitNodeMultipliers.ScriptHackMoney,
],
[
"Hacking Growth",
player.hacking_grow_mult,
player.hacking_grow_mult * BitNodeMultipliers.ServerGrowthRate,
],
]}
color={Settings.theme.hack}
/>
<MultiplierTable
rows={[
[
"Hacking Level",
player.hacking_mult,
player.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier,
],
[
"Hacking Experience",
player.hacking_exp_mult,
player.hacking_exp_mult * BitNodeMultipliers.HackExpGain,
],
]}
color={Settings.theme.hack}
/>
<MultiplierTable
rows={[
[
"Strength Level",
player.strength_mult,
player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience", player.strength_exp_mult],
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Defense Level",
player.defense_mult,
player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier,
],
["Defense Experience", player.defense_exp_mult],
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Dexterity Level",
player.dexterity_mult,
player.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier,
],
["Dexterity Experience", player.dexterity_exp_mult],
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Agility Level",
player.agility_mult,
player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier,
],
["Agility Experience", player.agility_exp_mult],
]}
color={Settings.theme.combat}
/>
<MultiplierTable
rows={[
[
"Charisma Level",
player.charisma_mult,
player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier,
],
["Charisma Experience", player.charisma_exp_mult],
]}
color={Settings.theme.cha}
noMargin
/>
</Box>
<MultiplierTable
rows={[
["Strength Level", player.strength_mult, player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier],
["Strength Experience", player.strength_exp_mult],
]}
/>
<br />
<MultiplierTable
rows={[
["Defense Level", player.defense_mult, player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier],
["Defense Experience", player.defense_exp_mult],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Dexterity Level",
player.dexterity_mult,
player.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier,
],
["Dexterity Experience", player.dexterity_exp_mult],
]}
/>
<br />
<MultiplierTable
rows={[
["Agility Level", player.agility_mult, player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier],
["Agility Experience", player.agility_exp_mult],
]}
/>
<br />
<MultiplierTable
rows={[
["Charisma Level", player.charisma_mult, player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier],
["Charisma Experience", player.charisma_exp_mult],
]}
/>
<br />
<MultiplierTable
rows={[
[
"Hacknet Node production",
player.hacknet_node_money_mult,
player.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney,
],
["Hacknet Node purchase cost", player.hacknet_node_purchase_cost_mult],
["Hacknet Node RAM upgrade cost", player.hacknet_node_ram_cost_mult],
["Hacknet Node Core purchase cost", player.hacknet_node_core_cost_mult],
["Hacknet Node level upgrade cost", player.hacknet_node_level_cost_mult],
]}
/>
<br />
<MultiplierTable
rows={[
["Company reputation gain", player.company_rep_mult],
[
"Faction reputation gain",
player.faction_rep_mult,
player.faction_rep_mult * BitNodeMultipliers.FactionWorkRepGain,
],
["Salary", player.work_money_mult, player.work_money_mult * BitNodeMultipliers.CompanyWorkMoney],
]}
/>
<br />
<MultiplierTable
rows={[
["Crime success", player.crime_success_mult],
["Crime money", player.crime_money_mult, player.crime_money_mult * BitNodeMultipliers.CrimeMoney],
]}
/>
<br />
<BladeburnerMults />
<Box>
<MultiplierTable
rows={[
[
"Hacknet Node production",
player.hacknet_node_money_mult,
player.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney,
],
["Hacknet Node purchase cost", player.hacknet_node_purchase_cost_mult],
["Hacknet Node RAM upgrade cost", player.hacknet_node_ram_cost_mult],
["Hacknet Node Core purchase cost", player.hacknet_node_core_cost_mult],
["Hacknet Node level upgrade cost", player.hacknet_node_level_cost_mult],
]}
color={Settings.theme.primary}
/>
<MultiplierTable
rows={[
["Company reputation gain", player.company_rep_mult],
[
"Faction reputation gain",
player.faction_rep_mult,
player.faction_rep_mult * BitNodeMultipliers.FactionWorkRepGain,
],
["Salary", player.work_money_mult, player.work_money_mult * BitNodeMultipliers.CompanyWorkMoney],
]}
color={Settings.theme.money}
/>
<MultiplierTable
rows={[
["Crime success", player.crime_success_mult],
["Crime money", player.crime_money_mult, player.crime_money_mult * BitNodeMultipliers.CrimeMoney],
]}
color={Settings.theme.combat}
/>
{player.canAccessBladeburner() && (
<MultiplierTable
rows={[
["Bladeburner Success Chance", player.bladeburner_success_chance_mult],
["Bladeburner Max Stamina", player.bladeburner_max_stamina_mult],
["Bladeburner Stamina Gain", player.bladeburner_stamina_gain_mult],
["Bladeburner Field Analysis", player.bladeburner_analysis_mult],
]}
color={Settings.theme.primary}
noMargin
/>
)}
</Box>
</Box>
</Paper>
</Box>
<br />
<Typography variant="h4">Misc</Typography>
<Box sx={{ mx: 2 }}>
<Typography>{`Servers owned: ${player.purchasedServers.length} / ${getPurchaseServerLimit()}`}</Typography>
<Hacknet />
<Typography>{`Augmentations installed: ${player.augmentations.length}`}</Typography>
<StatsTable rows={timeRows} />
<Box sx={{ mb: 1 }}>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Time Played</Typography>
<Table>
<TableBody>
{timeRows.map(([name, content]) => (
<StatsRow key={name} name={name} color={Settings.theme.primary} data={{ content: content }} />
))}
</TableBody>
</Table>
</Paper>
</Box>
<br />
<CurrentBitNode />
<MoneyModal open={moneyOpen} onClose={() => setMoneyOpen(false)} />
</>
<EmployersModal open={employersOpen} onClose={() => setEmployersOpen(false)} />
</Container>
);
}
+3 -4
View File
@@ -22,7 +22,7 @@ import { buyStock, sellStock, shortStock, sellShort } from "../StockMarket/Buyin
import {
cancelOrder,
eventEmitterForUiReset,
initStockMarketFnForReact,
initStockMarketFn,
placeOrder,
StockMarket,
} from "../StockMarket/StockMarket";
@@ -310,7 +310,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
function softReset(): void {
dialogBoxCreate("Soft Reset!");
prestigeAugmentation();
installAugmentations(true);
resetErrorBoundary();
Router.toTerminal();
}
@@ -445,7 +445,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
buyStockShort={shortStock}
cancelOrder={cancelOrder}
eventEmitterForReset={eventEmitterForUiReset}
initStockMarket={initStockMarketFnForReact}
initStockMarket={initStockMarketFn}
p={player}
placeOrder={placeOrder}
sellStockLong={sellStock}
@@ -491,7 +491,6 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
}}
installAugmentationsFn={() => {
installAugmentations();
Router.toTerminal();
}}
/>
);
+27 -3
View File
@@ -27,7 +27,7 @@ import PaletteIcon from "@mui/icons-material/Palette";
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
import { ConfirmationModal } from "./ConfirmationModal";
import { SnackbarEvents } from "./Snackbar";
import { SnackbarEvents, ToastVariant } from "./Snackbar";
import { Settings } from "../../Settings/Settings";
import { DeleteGameButton } from "./DeleteGameButton";
@@ -64,6 +64,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const importInput = useRef<HTMLInputElement>(null);
const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);
const [recentScriptsSize, setRecentScriptsSize] = useState(Settings.MaxRecentScriptsCapacity);
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
@@ -79,6 +80,11 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
Settings.CodeInstructionRunTime = newValue as number;
}
function handleRecentScriptsSizeChange(event: any, newValue: number | number[]): void {
setRecentScriptsSize(newValue as number);
Settings.MaxRecentScriptsCapacity = newValue as number;
}
function handleLogSizeChange(event: any, newValue: number | number[]): void {
setLogSize(newValue as number);
Settings.MaxLogCapacity = newValue as number;
@@ -123,7 +129,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
setImportData(data);
setImportSaveOpen(true);
} catch (ex: any) {
SnackbarEvents.emit(ex.toString(), "error", 5000);
SnackbarEvents.emit(ex.toString(), ToastVariant.ERROR, 5000);
}
}
@@ -133,7 +139,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
try {
await saveObject.importGame(importData.base64);
} catch (ex: any) {
SnackbarEvents.emit(ex.toString(), "error", 5000);
SnackbarEvents.emit(ex.toString(), ToastVariant.ERROR, 5000);
}
setImportSaveOpen(false);
@@ -176,6 +182,24 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
max={100}
valueLabelDisplay="auto"
/>
<Tooltip
title={
<Typography>
The maximum number of recently killed script entries being tracked. Setting this too high can
cause the game to use a lot of memory.
</Typography>
}
>
<Typography>Recently killed scripts size</Typography>
</Tooltip>
<Slider
value={recentScriptsSize}
onChange={handleRecentScriptsSizeChange}
step={25}
min={0}
max={500}
valueLabelDisplay="auto"
/>
<Tooltip
title={
<Typography>
+8 -3
View File
@@ -10,6 +10,13 @@ interface IProps {
children: React.ReactNode | React.ReactNode[];
}
export enum ToastVariant {
SUCCESS = "success",
WARNING = "warning",
ERROR = "error",
INFO = "info",
}
const useStyles = makeStyles(() => ({
snackbar: {
// Log popup z-index increments, so let's add a padding to be well above them.
@@ -36,9 +43,7 @@ export function SnackbarProvider(props: IProps): React.ReactElement {
);
}
export const SnackbarEvents = new EventEmitter<
[string | React.ReactNode, "success" | "warning" | "error" | "info", number]
>();
export const SnackbarEvents = new EventEmitter<[string | React.ReactNode, ToastVariant, number]>();
export function Snackbar(): React.ReactElement {
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
+4 -2
View File
@@ -17,9 +17,10 @@ interface IProps {
color: string;
classes?: any;
data: ITableRowData;
children?: React.ReactElement;
}
export const StatsRow = ({ name, color, classes = useStyles(), data }: IProps): React.ReactElement => {
export const StatsRow = ({ name, color, classes = useStyles(), children, data }: IProps): React.ReactElement => {
let content;
if (data.content !== undefined) {
@@ -36,7 +37,8 @@ export const StatsRow = ({ name, color, classes = useStyles(), data }: IProps):
<Typography style={{ color: color }}>{name}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography style={{ color: color }}>{content}</Typography>
{content ? <Typography style={{ color: color }}>{content}</Typography> : <></>}
{children}
</TableCell>
</TableRow>
);
+2 -2
View File
@@ -504,7 +504,7 @@ export function WorkInProgressRoot(): React.ReactElement {
<Grid container direction="column" justifyContent="center" alignItems="center" style={{ minHeight: "100vh" }}>
<Grid item>
<Typography>
You are currently working on crafting {player.graftAugmentationName}.
You are currently working on grafting {player.graftAugmentationName}.
<br />
<br />
You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}
@@ -519,7 +519,7 @@ export function WorkInProgressRoot(): React.ReactElement {
</Grid>
<Grid item>
<Button sx={{ mx: 2 }} onClick={cancel}>
Cancel work on crafting Augmentation
Cancel work on grafting Augmentation
</Button>
<Button onClick={unfocus}>Do something else simultaneously</Button>
</Grid>
+97
View File
@@ -0,0 +1,97 @@
// by Discord: H3draut3r#6722, feel free to ask me any questions. i probably don't know the answer 🤣
export function HammingEncode(value: number): string {
// encoding following Hammings rule
function HammingSumOfParity(_lengthOfDBits: number): number {
// will calculate the needed amount of parityBits 'without' the "overall"-Parity (that math took me 4 Days to get it working)
return _lengthOfDBits < 3 || _lengthOfDBits == 0 // oh and of course using ternary operators, it's a pretty neat function
? _lengthOfDBits == 0
? 0
: _lengthOfDBits + 1
: // the following math will only work, if the length is greater equal 3, otherwise it's "kind of" broken :D
Math.ceil(Math.log2(_lengthOfDBits * 2)) <=
Math.ceil(Math.log2(1 + _lengthOfDBits + Math.ceil(Math.log2(_lengthOfDBits))))
? Math.ceil(Math.log2(_lengthOfDBits) + 1)
: Math.ceil(Math.log2(_lengthOfDBits));
}
const _data = value.toString(2).split(""); // first, change into binary string, then create array with 1 bit per index
const _sumParity: number = HammingSumOfParity(_data.length); // get the sum of needed parity bits (for later use in encoding)
const count = (arr: Array<string>, val: string): number =>
arr.reduce((a: number, v: string) => (v === val ? a + 1 : a), 0);
// function count for specific entries in the array, for later use
const _build = ["x", "x", ..._data.splice(0, 1)]; // init the "pre-build"
for (let i = 2; i < _sumParity; i++) {
// add new paritybits and the corresponding data bits (pre-building array)
_build.push("x", ..._data.splice(0, Math.pow(2, i) - 1));
}
// now the "calculation"... get the paritybits ('x') working
for (const index of _build.reduce(function (a: Array<number>, e: string, i: number) {
if (e == "x") a.push(i);
return a;
}, [])) {
// that reduce will result in an array of index numbers where the "x" is placed
const _tempcount = index + 1; // set the "stepsize" for the parityBit
const _temparray = []; // temporary array to store the extracted bits
const _tempdata = [..._build]; // only work with a copy of the _build
while (_tempdata[index] !== undefined) {
// as long as there are bits on the starting index, do "cut"
const _temp: Array<string> = _tempdata.splice(index, _tempcount * 2); // cut stepsize*2 bits, then...
_temparray.push(..._temp.splice(0, _tempcount)); // ... cut the result again and keep the first half
}
_temparray.splice(0, 1); // remove first bit, which is the parity one
_build[index] = (count(_temparray, "1") % 2).toString(); // count with remainder of 2 and"toString" to store the parityBit
} // parity done, now the "overall"-parity is set
_build.unshift((count(_build, "1") % 2).toString()); // has to be done as last element
return _build.join(""); // return the _build as string
}
export function HammingDecode(_data: string): number {
//check for altered bit and decode
const _build = _data.split(""); // ye, an array for working, again
const _testArray = []; //for the "truthtable". if any is false, the data has an altered bit, will check for and fix it
const _sumParity = Math.ceil(Math.log2(_data.length)); // sum of parity for later use
const count = (arr: Array<string>, val: string): number =>
arr.reduce((a: number, v: string) => (v === val ? a + 1 : a), 0);
// the count.... again ;)
let _overallParity = _build.splice(0, 1).join(""); // store first index, for checking in next step and fix the _build properly later on
_testArray.push(_overallParity == (count(_build, "1") % 2).toString() ? true : false); // first check with the overall parity bit
for (let i = 0; i < _sumParity; i++) {
// for the rest of the remaining parity bits we also "check"
const _tempIndex = Math.pow(2, i) - 1; // get the parityBits Index
const _tempStep = _tempIndex + 1; // set the stepsize
const _tempData = [..._build]; // get a "copy" of the build-data for working
const _tempArray = []; // init empty array for "testing"
while (_tempData[_tempIndex] != undefined) {
// extract from the copied data until the "starting" index is undefined
const _temp = [..._tempData.splice(_tempIndex, _tempStep * 2)]; // extract 2*stepsize
_tempArray.push(..._temp.splice(0, _tempStep)); // and cut again for keeping first half
}
const _tempParity = _tempArray.shift(); // and again save the first index separated for checking with the rest of the data
_testArray.push(_tempParity == (count(_tempArray, "1") % 2).toString() ? true : false);
// is the _tempParity the calculated data? push answer into the 'truthtable'
}
let _fixIndex = 0; // init the "fixing" index and start with 0
for (let i = 1; i < _sumParity + 1; i++) {
// simple binary adding for every boolean in the _testArray, starting from 2nd index of it
_fixIndex += _testArray[i] ? 0 : Math.pow(2, i) / 2;
}
_build.unshift(_overallParity); // now we need the "overall" parity back in it's place
// try fix the actual encoded binary string if there is an error
if (_fixIndex > 0 && _testArray[0] == false) {
// if the overall is false and the sum of calculated values is greater equal 0, fix the corresponding hamming-bit
_build[_fixIndex] = _build[_fixIndex] == "0" ? "1" : "0";
} else if (_testArray[0] == false) {
// otherwise, if the the overall_parity is the only wrong, fix that one
_overallParity = _overallParity == "0" ? "1" : "0";
} else if (_testArray[0] == true && _testArray.some((truth) => truth == false)) {
return 0; // uhm, there's some strange going on... 2 bits are altered? How? This should not happen 👀
}
// oof.. halfway through... we fixed an possible altered bit, now "extract" the parity-bits from the _build
for (let i = _sumParity; i >= 0; i--) {
// start from the last parity down the 2nd index one
_build.splice(Math.pow(2, i), 1);
}
_build.splice(0, 1); // remove the overall parity bit and we have our binary value
return parseInt(_build.join(""), 2); // parse the integer with redux 2 and we're done!
}
+133
View File
@@ -0,0 +1,133 @@
/** Binary heap. */
abstract class BinHeap<T> {
/**
* Heap data array consisting of [weight, payload] pairs, arranged by weight
* to satisfy heap condition.
*
* Encodes the binary tree by storing tree root at index 0 and
* left child of element i at `i * 2 + 1` and
* right child of element i at `i * 2 + 2`.
*/
protected data: [number, T][];
constructor() {
this.data = [];
}
/** Get number of elements in the heap. */
public get size(): number {
return this.data.length;
}
/** Add a new element to the heap. */
public push(value: T, weight: number): void {
const i = this.data.length;
this.data[i] = [weight, value];
this.heapifyUp(i);
}
/** Get the value of the root-most element of the heap, without changing the heap. */
public peek(): T | undefined {
if (this.data.length == 0) return undefined;
return this.data[0][1];
}
/** Remove the root-most element of the heap and return the removed element's value. */
public pop(): T | undefined {
if (this.data.length == 0) return undefined;
const value = this.data[0][1];
this.data[0] = this.data[this.data.length - 1];
this.data.length = this.data.length - 1;
this.heapifyDown(0);
return value;
}
/** Change the weight of an element in the heap. */
public changeWeight(predicate: (value: T) => boolean, weight: number): void {
// Find first element with matching value, if any
const i = this.data.findIndex((e) => predicate(e[1]));
if (i == -1) return;
// Update that element's weight
this.data[i][0] = weight;
// And re-heapify if needed
const p = Math.floor((i - 1) / 2);
if (!this.heapOrderABeforeB(this.data[p][0], this.data[i][0]))
// Needs to shift root-wards?
this.heapifyUp(i);
// Try shifting deeper
else this.heapifyDown(i);
}
/** Restore heap condition, starting at index i and traveling towards root. */
protected heapifyUp(i: number): void {
// Swap the new element up towards root until it reaches root position or
// settles under under a suitable parent
while (i > 0) {
const p = Math.floor((i - 1) / 2);
// Reached heap-ordered state already?
if (this.heapOrderABeforeB(this.data[p][0], this.data[i][0])) break;
// Swap
const tmp = this.data[p];
this.data[p] = this.data[i];
this.data[i] = tmp;
// And repeat at parent index
i = p;
}
}
/** Restore heap condition, starting at index i and traveling away from root. */
protected heapifyDown(i: number): void {
// Swap the shifted element down in the heap until it either reaches the
// bottom layer or is in correct order relative to it's children
while (i < this.data.length) {
const l = i * 2 + 1;
const r = i * 2 + 2;
let toSwap = i;
// Find which one of element i and it's children should be closest to root
if (l < this.data.length && this.heapOrderABeforeB(this.data[l][0], this.data[toSwap][0])) toSwap = l;
if (r < this.data.length && this.heapOrderABeforeB(this.data[r][0], this.data[toSwap][0])) toSwap = r;
// Already in order?
if (i == toSwap) break;
// Not in order. Swap child that should be closest to root up to 'i' and repeat
const tmp = this.data[toSwap];
this.data[toSwap] = this.data[i];
this.data[i] = tmp;
i = toSwap;
}
}
/**
* Should element with weight `weightA` be closer to root than element with
* weight `weightB`?
*/
protected abstract heapOrderABeforeB(weightA: number, weightB: number): boolean;
}
/** Binary max-heap. */
export class MaxHeap<T> extends BinHeap<T> {
heapOrderABeforeB(weightA: number, weightB: number): boolean {
return weightA > weightB;
}
}
/** Binary min-heap. */
export class MinHeap<T> extends BinHeap<T> {
heapOrderABeforeB(weightA: number, weightB: number): boolean {
return weightA < weightB;
}
}
+6
View File
@@ -0,0 +1,6 @@
export function checkEnum<T extends string, TEnumValue extends string>(
enumVariable: { [key in T]: TEnumValue },
value: string,
): value is TEnumValue {
return Object.values(enumVariable).includes(value);
}