mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
Compare commits
2 Commits
d1b6acc57a
...
better-rer
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bb0bc5728 | ||
|
|
80d566f051 |
12
src/Hacknet/ui/Components/HacknetExpenses.tsx
Normal file
12
src/Hacknet/ui/Components/HacknetExpenses.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from "react";
|
||||
|
||||
import { PlayerObject } from "../../../PersonObjects/Player/PlayerObject";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { usePlayerSelector } from "../../../utils/PlayerExternalStore";
|
||||
|
||||
const selectHacknetExpenses = (p: PlayerObject) => -p.moneySourceA.hacknet_expenses || 0;
|
||||
|
||||
export function HacknetExpenses(): React.ReactElement {
|
||||
const spent = usePlayerSelector(selectHacknetExpenses);
|
||||
return <Money key="money" money={spent} />;
|
||||
}
|
||||
11
src/Hacknet/ui/Components/HacknetProduced.tsx
Normal file
11
src/Hacknet/ui/Components/HacknetProduced.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
import { PlayerObject } from "../../../PersonObjects/Player/PlayerObject";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { usePlayerSelector } from "../../../utils/PlayerExternalStore";
|
||||
|
||||
const selectHacknetMoney = (p: PlayerObject) => p.moneySourceA.hacknet;
|
||||
|
||||
export function HacknetProduced(): React.ReactElement {
|
||||
const produced = usePlayerSelector(selectHacknetMoney);
|
||||
return <Money key="money" money={produced} />;
|
||||
}
|
||||
16
src/Hacknet/ui/Components/HacknetServerLevel.tsx
Normal file
16
src/Hacknet/ui/Components/HacknetServerLevel.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { useCallback } from "react";
|
||||
import { PlayerObject } from "../../../PersonObjects/Player/PlayerObject";
|
||||
import { usePlayerSelector } from "../../../utils/PlayerExternalStore";
|
||||
import { safeGetHacknetServer } from "../utils";
|
||||
|
||||
interface IProps {
|
||||
index: number;
|
||||
}
|
||||
|
||||
export function HacknetServerLevel({ index }: IProps): React.ReactElement {
|
||||
const level = usePlayerSelector(
|
||||
useCallback((p: PlayerObject) => safeGetHacknetServer(p, index)?.level ?? "???", [index]),
|
||||
);
|
||||
return <>{level}</>;
|
||||
}
|
||||
34
src/Hacknet/ui/Components/HashTotalProduction.tsx
Normal file
34
src/Hacknet/ui/Components/HashTotalProduction.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import { hasHacknetServers } from "../../../Hacknet/HacknetHelpers";
|
||||
import { HacknetNode } from "../../../Hacknet/HacknetNode";
|
||||
import { HacknetServer } from "../../../Hacknet/HacknetServer";
|
||||
import { PlayerObject } from "../../../PersonObjects/Player/PlayerObject";
|
||||
import { GetServer } from "../../../Server/AllServers";
|
||||
import { usePlayerSelector } from "../../../utils/PlayerExternalStore";
|
||||
import { HashRate } from "../../../ui/React/HashRate";
|
||||
|
||||
const selectTotalProduction = (p: PlayerObject) => {
|
||||
let totalProduction = 0;
|
||||
for (let i = 0; i < p.hacknetNodes.length; ++i) {
|
||||
const node = p.hacknetNodes[i];
|
||||
if (hasHacknetServers()) {
|
||||
if (node instanceof HacknetNode) throw new Error("node was hacknet node"); // should never happen
|
||||
const hserver = GetServer(node);
|
||||
if (!(hserver instanceof HacknetServer)) throw new Error("node was not hacknet server"); // should never happen
|
||||
if (hserver) {
|
||||
totalProduction += hserver.hashRate;
|
||||
} else {
|
||||
console.warn(`Could not find Hacknet Server object in AllServers map (i=${i})`);
|
||||
}
|
||||
} else {
|
||||
if (typeof node === "string") throw new Error("node was ip string"); // should never happen
|
||||
totalProduction += node.moneyGainRatePerSecond;
|
||||
}
|
||||
}
|
||||
return totalProduction;
|
||||
};
|
||||
|
||||
export function HashTotalProduction(): React.ReactElement {
|
||||
const prod = usePlayerSelector(selectTotalProduction);
|
||||
return <HashRate key="hashRate" hashes={prod} />;
|
||||
}
|
||||
17
src/Hacknet/ui/Components/MoneyButton.tsx
Normal file
17
src/Hacknet/ui/Components/MoneyButton.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { Button, ButtonProps } from "@mui/material";
|
||||
import { usePlayerSelector } from "../../../utils/PlayerExternalStore";
|
||||
import { PlayerObject } from "../../../PersonObjects/Player/PlayerObject";
|
||||
|
||||
type MoneyButtonProps = {
|
||||
cost: number;
|
||||
} & ButtonProps;
|
||||
|
||||
export function MoneyButton(props: MoneyButtonProps): React.ReactElement {
|
||||
const canAfford = usePlayerSelector(React.useCallback((p: PlayerObject) => p.canAfford(props.cost), [props.cost]));
|
||||
return (
|
||||
<Button disabled={!canAfford || props.disabled} {...props}>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
25
src/Hacknet/ui/Components/MoneyCost.tsx
Normal file
25
src/Hacknet/ui/Components/MoneyCost.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from "react";
|
||||
import { formatMoney } from "../../../ui/formatNumber";
|
||||
import type { Theme } from "@mui/material/styles";
|
||||
import { makeStyles } from "tss-react/mui";
|
||||
import { usePlayerSelector } from "../../../utils/PlayerExternalStore";
|
||||
import { PlayerObject } from "../../../PersonObjects/Player/PlayerObject";
|
||||
|
||||
const useStyles = makeStyles()((theme: Theme) => ({
|
||||
unbuyable: {
|
||||
color: theme.palette.action.disabled,
|
||||
},
|
||||
money: {
|
||||
color: theme.colors.money,
|
||||
},
|
||||
}));
|
||||
|
||||
interface IProps {
|
||||
cost: number;
|
||||
}
|
||||
|
||||
export function MoneyCost({ cost }: IProps): React.ReactElement {
|
||||
const { classes } = useStyles();
|
||||
const canAfford = usePlayerSelector(React.useCallback((p: PlayerObject) => p.canAfford(cost), [cost]));
|
||||
return <span className={canAfford ? classes.money : classes.unbuyable}>{formatMoney(cost)}</span>;
|
||||
}
|
||||
11
src/Hacknet/ui/Components/PlayerHashCapacity.tsx
Normal file
11
src/Hacknet/ui/Components/PlayerHashCapacity.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
import { PlayerObject } from "../../../PersonObjects/Player/PlayerObject";
|
||||
import { Hashes } from "../../../ui/React/Hashes";
|
||||
import { usePlayerSelector } from "../../../utils/PlayerExternalStore";
|
||||
|
||||
const selectHashCapacity = (p: PlayerObject) => p.hashManager.capacity;
|
||||
|
||||
export function PlayerHashCapacity(): React.ReactElement {
|
||||
const capacity = usePlayerSelector(selectHashCapacity);
|
||||
return <Hashes hashes={capacity} />;
|
||||
}
|
||||
11
src/Hacknet/ui/Components/PlayerHashes.tsx
Normal file
11
src/Hacknet/ui/Components/PlayerHashes.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
import { PlayerObject } from "../../../PersonObjects/Player/PlayerObject";
|
||||
import { Hashes } from "../../../ui/React/Hashes";
|
||||
import { usePlayerSelector } from "../../../utils/PlayerExternalStore";
|
||||
|
||||
const selectHashes = (p: PlayerObject) => p.hashManager.hashes;
|
||||
|
||||
export function PlayerHashes(): React.ReactElement {
|
||||
const hashes = usePlayerSelector(selectHashes);
|
||||
return <Hashes hashes={hashes} />;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { Button, Tooltip } from "@mui/material";
|
||||
import { HacknetServerConstants } from "../../data/Constants";
|
||||
import { PlayerObject } from "../../../PersonObjects/Player/PlayerObject";
|
||||
import { usePlayerSelector } from "../../../utils/PlayerExternalStore";
|
||||
import { getMaxNumberLevelUpgrades, purchaseLevelUpgrade } from "../../HacknetHelpers";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { UpgradeHacknetServerLevelTooltip } from "./UpgradeHacknetServerLevelTooltip";
|
||||
import { HacknetServer } from "../../HacknetServer";
|
||||
|
||||
interface IProps {
|
||||
node: HacknetServer;
|
||||
purchaseMult: number | string;
|
||||
}
|
||||
|
||||
const selectHacknetNodeLevelCost = (p: PlayerObject) => p.mults.hacknet_node_level_cost;
|
||||
const selectMoney = (p: PlayerObject) => p.money;
|
||||
|
||||
export function UpgradeHacknetServerLevelButton({ purchaseMult, node }: IProps): React.ReactElement {
|
||||
const level = usePlayerSelector(useCallback((): number => node.level, [node]));
|
||||
const hacknet_node_level_cost = usePlayerSelector(selectHacknetNodeLevelCost);
|
||||
usePlayerSelector(selectMoney); // we have to hook on money change for the max level algorithm
|
||||
|
||||
function upgradeLevelOnClick(): void {
|
||||
let numUpgrades = purchaseMult;
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetServerConstants.MaxLevel);
|
||||
}
|
||||
purchaseLevelUpgrade(node, numUpgrades as number);
|
||||
}
|
||||
|
||||
if (level >= HacknetServerConstants.MaxLevel) {
|
||||
return <Button disabled>MAX LEVEL</Button>;
|
||||
} else {
|
||||
let multiplier = 0;
|
||||
if (purchaseMult === "MAX") {
|
||||
multiplier = getMaxNumberLevelUpgrades(node, HacknetServerConstants.MaxLevel);
|
||||
} else {
|
||||
const levelsToMax = HacknetServerConstants.MaxLevel - level;
|
||||
multiplier = Math.min(levelsToMax, purchaseMult as number);
|
||||
}
|
||||
|
||||
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, hacknet_node_level_cost) ?? 0;
|
||||
return (
|
||||
<Tooltip title={<UpgradeHacknetServerLevelTooltip purchaseMult={purchaseMult} node={node} />}>
|
||||
<Button onClick={upgradeLevelOnClick}>
|
||||
+{multiplier} -
|
||||
<Money money={upgradeLevelCost} forPurchase={true} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Typography } from "@mui/material";
|
||||
import React, { useCallback } from "react";
|
||||
import { calculateHashGainRate } from "../../formulas/HacknetServers";
|
||||
import { PlayerObject } from "../../../PersonObjects/Player/PlayerObject";
|
||||
import { HashRate } from "../../../ui/React/HashRate";
|
||||
import { arrayShallowEquals, usePlayerSelector } from "../../../utils/PlayerExternalStore";
|
||||
import { getMaxNumberLevelUpgrades } from "../../HacknetHelpers";
|
||||
import { HacknetServerConstants } from "../../data/Constants";
|
||||
import { HacknetServer } from "../../HacknetServer";
|
||||
|
||||
interface IProps {
|
||||
node: HacknetServer;
|
||||
purchaseMult: number | string;
|
||||
}
|
||||
|
||||
const selectHacknetNodeMoney = (p: PlayerObject) => p.mults.hacknet_node_money;
|
||||
|
||||
export function UpgradeHacknetServerLevelTooltip({ purchaseMult, node }: IProps): React.ReactElement {
|
||||
const [level, ramUsed, maxRam, cores] = usePlayerSelector(
|
||||
useCallback((): [number, number, number, number] => [node.level, node.ramUsed, node.maxRam, node.cores], [node]),
|
||||
arrayShallowEquals,
|
||||
);
|
||||
|
||||
const hacknetNodeMoney = usePlayerSelector(selectHacknetNodeMoney);
|
||||
|
||||
let multiplier = 0;
|
||||
if (purchaseMult === "MAX") {
|
||||
multiplier = getMaxNumberLevelUpgrades(node, HacknetServerConstants.MaxLevel);
|
||||
} else {
|
||||
const levelsToMax = HacknetServerConstants.MaxLevel - level;
|
||||
multiplier = Math.min(levelsToMax, purchaseMult as number);
|
||||
}
|
||||
|
||||
const base_increase =
|
||||
calculateHashGainRate(level + multiplier, 0, maxRam, cores, hacknetNodeMoney) -
|
||||
calculateHashGainRate(level, 0, maxRam, cores, hacknetNodeMoney);
|
||||
const modded_increase = (base_increase * (maxRam - ramUsed)) / maxRam;
|
||||
return (
|
||||
<Typography>
|
||||
+<HashRate hashes={modded_increase} /> (effective increase, taking current RAM usage into account)
|
||||
<br />
|
||||
<span style={{ opacity: 0.5 }}>
|
||||
+<HashRate hashes={base_increase} />
|
||||
</span>{" "}
|
||||
(base increase, attained when no script is running)
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
@@ -35,13 +35,11 @@ import { calculateMoneyGainRate } from "../formulas/HacknetNodes";
|
||||
interface IProps {
|
||||
node: HacknetNode;
|
||||
purchaseMultiplier: number | "MAX";
|
||||
rerender: () => void;
|
||||
}
|
||||
|
||||
export function HacknetNodeElem(props: IProps): React.ReactElement {
|
||||
const node = props.node;
|
||||
const purchaseMult = props.purchaseMultiplier;
|
||||
const rerender = props.rerender;
|
||||
|
||||
// Upgrade Level Button
|
||||
let upgradeLevelButton;
|
||||
@@ -79,7 +77,6 @@ export function HacknetNodeElem(props: IProps): React.ReactElement {
|
||||
const numUpgrades =
|
||||
purchaseMult === "MAX" ? getMaxNumberLevelUpgrades(node, HacknetNodeConstants.MaxLevel) : purchaseMult;
|
||||
purchaseLevelUpgrade(node, numUpgrades);
|
||||
rerender();
|
||||
}
|
||||
|
||||
let upgradeRAMButton;
|
||||
@@ -121,14 +118,12 @@ export function HacknetNodeElem(props: IProps): React.ReactElement {
|
||||
const numUpgrades =
|
||||
purchaseMult === "MAX" ? getMaxNumberRamUpgrades(node, HacknetNodeConstants.MaxRam) : purchaseMult;
|
||||
purchaseRamUpgrade(node, numUpgrades);
|
||||
rerender();
|
||||
}
|
||||
|
||||
function upgradeCoresOnClick(): void {
|
||||
const numUpgrades =
|
||||
purchaseMult === "MAX" ? getMaxNumberCoreUpgrades(node, HacknetNodeConstants.MaxCores) : purchaseMult;
|
||||
purchaseCoreUpgrade(node, numUpgrades);
|
||||
rerender();
|
||||
}
|
||||
let upgradeCoresButton;
|
||||
if (node.cores >= HacknetNodeConstants.MaxCores) {
|
||||
|
||||
@@ -25,16 +25,19 @@ import Typography from "@mui/material/Typography";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Button from "@mui/material/Button";
|
||||
import { Box } from "@mui/material";
|
||||
import { useCycleRerender } from "../../ui/React/hooks";
|
||||
import { usePlayerSelector } from "../../utils/PlayerExternalStore";
|
||||
import { PlayerObject } from "src/PersonObjects/Player/PlayerObject";
|
||||
|
||||
const selectHacknetNodecount = (p: PlayerObject) => p.hacknetNodes.length;
|
||||
|
||||
/** Root React Component for the Hacknet Node UI */
|
||||
export function HacknetRoot(): React.ReactElement {
|
||||
const [open, setOpen] = useState(false);
|
||||
const rerender = useCycleRerender();
|
||||
const [purchaseMultiplier, setPurchaseMultiplier] = useState<number | "MAX">(PurchaseMultipliers.x1);
|
||||
const hacknetNodeCount = usePlayerSelector(selectHacknetNodecount);
|
||||
|
||||
let totalProduction = 0;
|
||||
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
|
||||
for (let i = 0; i < hacknetNodeCount; ++i) {
|
||||
const node = Player.hacknetNodes[i];
|
||||
if (hasHacknetServers()) {
|
||||
if (node instanceof HacknetNode) throw new Error("node was hacknet node"); // should never happen
|
||||
@@ -53,7 +56,6 @@ export function HacknetRoot(): React.ReactElement {
|
||||
|
||||
function handlePurchaseButtonClick(): void {
|
||||
purchaseHacknet();
|
||||
rerender();
|
||||
}
|
||||
|
||||
// Cost to purchase a new Hacknet Node
|
||||
@@ -73,7 +75,7 @@ export function HacknetRoot(): React.ReactElement {
|
||||
];
|
||||
|
||||
// HacknetNode components
|
||||
const nodes = Player.hacknetNodes.map((node: string | HacknetNode) => {
|
||||
const nodes = Player.hacknetNodes.map((node: string | HacknetNode, index: number) => {
|
||||
if (hasHacknetServers()) {
|
||||
if (node instanceof HacknetNode) throw new Error("node was hacknet node"); // should never happen
|
||||
const hserver = GetServer(node);
|
||||
@@ -84,16 +86,14 @@ export function HacknetRoot(): React.ReactElement {
|
||||
return (
|
||||
<HacknetServerElem
|
||||
key={hserver.hostname}
|
||||
index={index}
|
||||
node={hserver}
|
||||
purchaseMultiplier={purchaseMultiplier}
|
||||
rerender={rerender}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
if (typeof node === "string") throw new Error("node was ip string"); // should never happen
|
||||
return (
|
||||
<HacknetNodeElem key={node.name} node={node} purchaseMultiplier={purchaseMultiplier} rerender={rerender} />
|
||||
);
|
||||
return <HacknetNodeElem key={node.name} node={node} purchaseMultiplier={purchaseMultiplier} />;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -6,11 +6,9 @@ import React from "react";
|
||||
|
||||
import { HacknetServerConstants } from "../data/Constants";
|
||||
import {
|
||||
getMaxNumberLevelUpgrades,
|
||||
getMaxNumberRamUpgrades,
|
||||
getMaxNumberCoreUpgrades,
|
||||
getMaxNumberCacheUpgrades,
|
||||
purchaseLevelUpgrade,
|
||||
purchaseRamUpgrade,
|
||||
purchaseCoreUpgrade,
|
||||
purchaseCacheUpgrade,
|
||||
@@ -34,65 +32,19 @@ import TableRow from "@mui/material/TableRow";
|
||||
import { formatRam } from "../../ui/formatNumber";
|
||||
import { calculateHashGainRate } from "../formulas/HacknetServers";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { HacknetServerLevel } from "./Components/HacknetServerLevel";
|
||||
import { UpgradeHacknetServerLevelButton } from "./Components/UpgradeHacknetServerLevelButton";
|
||||
|
||||
interface IProps {
|
||||
index: number;
|
||||
node: HacknetServer;
|
||||
purchaseMultiplier: number | string;
|
||||
rerender: () => void;
|
||||
}
|
||||
|
||||
export function HacknetServerElem(props: IProps): React.ReactElement {
|
||||
const node = props.node;
|
||||
const purchaseMult = props.purchaseMultiplier;
|
||||
const rerender = props.rerender;
|
||||
|
||||
// Upgrade Level Button
|
||||
let upgradeLevelButton;
|
||||
if (node.level >= HacknetServerConstants.MaxLevel) {
|
||||
upgradeLevelButton = <Button disabled>MAX LEVEL</Button>;
|
||||
} else {
|
||||
let multiplier = 0;
|
||||
if (purchaseMult === "MAX") {
|
||||
multiplier = getMaxNumberLevelUpgrades(node, HacknetServerConstants.MaxLevel);
|
||||
} else {
|
||||
const levelsToMax = HacknetServerConstants.MaxLevel - node.level;
|
||||
multiplier = Math.min(levelsToMax, purchaseMult as number);
|
||||
}
|
||||
|
||||
const base_increase =
|
||||
calculateHashGainRate(node.level + multiplier, 0, node.maxRam, node.cores, Player.mults.hacknet_node_money) -
|
||||
calculateHashGainRate(node.level, 0, node.maxRam, node.cores, Player.mults.hacknet_node_money);
|
||||
const modded_increase = (base_increase * (node.maxRam - node.ramUsed)) / node.maxRam;
|
||||
|
||||
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player.mults.hacknet_node_level_cost);
|
||||
upgradeLevelButton = (
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
+<HashRate hashes={modded_increase} /> (effective increase, taking current RAM usage into account)
|
||||
<br />
|
||||
<span style={{ opacity: 0.5 }}>
|
||||
+<HashRate hashes={base_increase} />
|
||||
</span>{" "}
|
||||
(base increase, attained when no script is running)
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Button onClick={upgradeLevelOnClick}>
|
||||
+{multiplier} -
|
||||
<Money money={upgradeLevelCost} forPurchase={true} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
function upgradeLevelOnClick(): void {
|
||||
let numUpgrades = purchaseMult;
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetServerConstants.MaxLevel);
|
||||
}
|
||||
purchaseLevelUpgrade(node, numUpgrades as number);
|
||||
rerender();
|
||||
}
|
||||
const index = props.index;
|
||||
|
||||
function upgradeRamOnClick(): void {
|
||||
let numUpgrades = purchaseMult;
|
||||
@@ -100,7 +52,6 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
|
||||
numUpgrades = getMaxNumberRamUpgrades(node, HacknetServerConstants.MaxRam);
|
||||
}
|
||||
purchaseRamUpgrade(node, numUpgrades as number);
|
||||
rerender();
|
||||
}
|
||||
// Upgrade RAM Button
|
||||
let upgradeRamButton;
|
||||
@@ -161,7 +112,6 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
|
||||
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetServerConstants.MaxCores);
|
||||
}
|
||||
purchaseCoreUpgrade(node, numUpgrades as number);
|
||||
rerender();
|
||||
}
|
||||
// Upgrade Cores Button
|
||||
let upgradeCoresButton;
|
||||
@@ -239,7 +189,6 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
|
||||
numUpgrades = getMaxNumberCacheUpgrades(node, HacknetServerConstants.MaxCache);
|
||||
}
|
||||
purchaseCacheUpgrade(node, numUpgrades as number);
|
||||
rerender();
|
||||
updateHashManagerCapacity();
|
||||
}
|
||||
|
||||
@@ -299,9 +248,13 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
|
||||
<Typography>Level:</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography>{node.level}</Typography>
|
||||
<Typography>
|
||||
<HacknetServerLevel index={index} />
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<UpgradeHacknetServerLevelButton purchaseMult={purchaseMult} node={node} />
|
||||
</TableCell>
|
||||
<TableCell>{upgradeLevelButton}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
|
||||
@@ -7,14 +7,15 @@
|
||||
import React from "react";
|
||||
|
||||
import { hasHacknetServers } from "../HacknetHelpers";
|
||||
import { Player } from "@player";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { MoneyRate } from "../../ui/React/MoneyRate";
|
||||
import { HashRate } from "../../ui/React/HashRate";
|
||||
import { Hashes } from "../../ui/React/Hashes";
|
||||
import { Paper, Typography } from "@mui/material";
|
||||
import { StatsTable } from "../../ui/React/StatsTable";
|
||||
import { Tooltip } from "@mui/material";
|
||||
import { HashTotalProduction } from "./Components/HashTotalProduction";
|
||||
import { HacknetExpenses } from "./Components/HacknetExpenses";
|
||||
import { HacknetProduced } from "./Components/HacknetProduced";
|
||||
import { PlayerHashes } from "./Components/PlayerHashes";
|
||||
import { PlayerHashCapacity } from "./Components/PlayerHashCapacity";
|
||||
|
||||
interface IProps {
|
||||
totalProduction: number;
|
||||
@@ -24,13 +25,13 @@ export function PlayerInfo(props: IProps): React.ReactElement {
|
||||
const hasServers = hasHacknetServers();
|
||||
|
||||
const rows: React.ReactNode[][] = [];
|
||||
rows.push(["Money Spent:", <Money key="money" money={-Player.moneySourceA.hacknet_expenses || 0} />]);
|
||||
rows.push(["Money Produced:", <Money key="money" money={Player.moneySourceA.hacknet} />]);
|
||||
rows.push(["Money Spent:", <HacknetExpenses key="expenses" />]);
|
||||
rows.push(["Money Produced:", <HacknetProduced key="money" />]);
|
||||
if (hasServers) {
|
||||
rows.push([
|
||||
"Hashes:",
|
||||
<span key={"hashes"}>
|
||||
<Hashes hashes={Player.hashManager.hashes} /> / <Hashes hashes={Player.hashManager.capacity} />
|
||||
<PlayerHashes /> / <PlayerHashCapacity />
|
||||
</span>,
|
||||
]);
|
||||
rows.push([
|
||||
@@ -44,7 +45,7 @@ export function PlayerInfo(props: IProps): React.ReactElement {
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<HashRate key="hashRate" hashes={props.totalProduction} />
|
||||
<HashTotalProduction />
|
||||
</span>
|
||||
</Tooltip>,
|
||||
]);
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import React from "react";
|
||||
|
||||
import { hasHacknetServers, hasMaxNumberHacknetServers } from "../HacknetHelpers";
|
||||
import { Player } from "@player";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
|
||||
import Button from "@mui/material/Button";
|
||||
import { MoneyCost } from "./Components/MoneyCost";
|
||||
import { MoneyButton } from "./Components/MoneyButton";
|
||||
|
||||
interface IProps {
|
||||
multiplier: number | string;
|
||||
@@ -23,7 +21,7 @@ export function PurchaseButton(props: IProps): React.ReactElement {
|
||||
text = (
|
||||
<>
|
||||
Purchase Hacknet Server -
|
||||
<Money money={cost} forPurchase={true} />
|
||||
<MoneyCost cost={cost} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -31,14 +29,14 @@ export function PurchaseButton(props: IProps): React.ReactElement {
|
||||
text = (
|
||||
<>
|
||||
Purchase Hacknet Node -
|
||||
<Money money={cost} forPurchase={true} />
|
||||
<MoneyCost cost={cost} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button disabled={!Player.canAfford(cost)} onClick={props.onClick}>
|
||||
<MoneyButton cost={cost} onClick={props.onClick}>
|
||||
{text}
|
||||
</Button>
|
||||
</MoneyButton>
|
||||
);
|
||||
}
|
||||
|
||||
19
src/Hacknet/ui/utils.ts
Normal file
19
src/Hacknet/ui/utils.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { PlayerObject } from "src/PersonObjects/Player/PlayerObject";
|
||||
import { HacknetServer } from "../HacknetServer";
|
||||
import { HacknetNode } from "../HacknetNode";
|
||||
import { GetServer } from "../../Server/AllServers";
|
||||
|
||||
export function safeGetHacknetServer(p: PlayerObject, index: number): HacknetServer | undefined {
|
||||
const node = p.hacknetNodes[index];
|
||||
if (node instanceof HacknetNode) return undefined;
|
||||
const hserver = GetServer(node);
|
||||
if (hserver == null) return undefined;
|
||||
if (!(hserver instanceof HacknetServer)) return undefined;
|
||||
return hserver;
|
||||
}
|
||||
|
||||
export function safeGetHacknetNode(p: PlayerObject, index: number): HacknetNode | undefined {
|
||||
const node = p.hacknetNodes[index];
|
||||
if (!(node instanceof HacknetNode)) return undefined;
|
||||
return node;
|
||||
}
|
||||
@@ -49,6 +49,7 @@ import { EventEmitter } from "./utils/EventEmitter";
|
||||
import { Companies } from "./Company/Companies";
|
||||
import { resetGoPromises } from "./Go/boardAnalysis/goAI";
|
||||
import { getRecordEntries } from "./Types/Record";
|
||||
import { runSelectors } from "./utils/PlayerExternalStore";
|
||||
|
||||
declare global {
|
||||
// This property is only available in the dev build
|
||||
@@ -421,6 +422,7 @@ const Engine = {
|
||||
if (GameCycleEvents.hasSubscribers()) {
|
||||
ReactDOM.unstable_batchedUpdates(() => {
|
||||
GameCycleEvents.emit();
|
||||
runSelectors();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
52
src/utils/PlayerExternalStore.ts
Normal file
52
src/utils/PlayerExternalStore.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Player } from "@player";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { PlayerObject } from "src/PersonObjects/Player/PlayerObject";
|
||||
|
||||
const trieq = <T>(a: T, b: T): boolean => a === b;
|
||||
|
||||
export const arrayShallowEquals = <T>(a: T[], b: T[]): boolean => {
|
||||
if (a.length != b.length) return false;
|
||||
return a.every((v, i) => b[i] === v);
|
||||
};
|
||||
|
||||
export const runSelectors = (): void => {
|
||||
for (const sub of subscriptions) sub(Player);
|
||||
};
|
||||
|
||||
const subscriptions: ((g: PlayerObject) => void)[] = [];
|
||||
|
||||
const subscribe = (check: (g: PlayerObject) => void): void => {
|
||||
subscriptions.push(check);
|
||||
};
|
||||
|
||||
const unsubscribe = (check: (g: PlayerObject) => void) => {
|
||||
return (): void => {
|
||||
let found = -1;
|
||||
for (let i = 0; i < subscriptions.length; i++) {
|
||||
if (subscriptions[i] !== check) continue;
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
if (found === -1) return;
|
||||
subscriptions[found] = subscriptions[subscriptions.length - 1];
|
||||
subscriptions.length -= 1;
|
||||
};
|
||||
};
|
||||
|
||||
const incr = (n: number): number => n + 1;
|
||||
|
||||
export const usePlayerSelector = <T>(f: (g: PlayerObject) => T, eq: (a: T, b: T) => boolean = trieq): T => {
|
||||
const [, setValue] = useState(0);
|
||||
const ref = useRef(f(Player));
|
||||
useEffect(() => {
|
||||
const check = (g: PlayerObject): void => {
|
||||
const next = f(g);
|
||||
if (eq(ref.current, next)) return;
|
||||
ref.current = next;
|
||||
setValue(incr);
|
||||
};
|
||||
subscribe(check);
|
||||
return unsubscribe(check);
|
||||
}, [f, eq]);
|
||||
return ref.current;
|
||||
};
|
||||
Reference in New Issue
Block a user