2nd commit

This commit is contained in:
hydroflame
2025-08-27 21:59:24 -04:00
parent 80d566f051
commit 9bb0bc5728
15 changed files with 289 additions and 82 deletions

View 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} />;
}

View 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} />;
}

View 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}</>;
}

View 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} />;
}

View 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>
);
}

View 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>;
}

View 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} />;
}

View 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} />;
}

View File

@@ -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}&nbsp;-&nbsp;
<Money money={upgradeLevelCost} forPurchase={true} />
</Button>
</Tooltip>
);
}
}

View File

@@ -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>
);
}

View File

@@ -75,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);
@@ -83,7 +83,14 @@ export function HacknetRoot(): React.ReactElement {
throw new Error(`Could not find Hacknet Server object in AllServers map for IP: ${node}`);
}
if (!(hserver instanceof HacknetServer)) throw new Error("node was not hacknet server"); // should never happen
return <HacknetServerElem key={hserver.hostname} node={hserver} purchaseMultiplier={purchaseMultiplier} />;
return (
<HacknetServerElem
key={hserver.hostname}
index={index}
node={hserver}
purchaseMultiplier={purchaseMultiplier}
/>
);
} else {
if (typeof node === "string") throw new Error("node was ip string"); // should never happen
return <HacknetNodeElem key={node.name} node={node} purchaseMultiplier={purchaseMultiplier} />;

View File

@@ -6,11 +6,9 @@ import React from "react";
import { HacknetServerConstants } from "../data/Constants";
import {
getMaxNumberLevelUpgrades,
getMaxNumberRamUpgrades,
getMaxNumberCoreUpgrades,
getMaxNumberCacheUpgrades,
purchaseLevelUpgrade,
purchaseRamUpgrade,
purchaseCoreUpgrade,
purchaseCacheUpgrade,
@@ -34,8 +32,11 @@ 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;
}
@@ -43,53 +44,7 @@ interface IProps {
export function HacknetServerElem(props: IProps): React.ReactElement {
const node = props.node;
const purchaseMult = props.purchaseMultiplier;
// 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}&nbsp;-&nbsp;
<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);
}
const index = props.index;
function upgradeRamOnClick(): void {
let numUpgrades = purchaseMult;
@@ -293,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>

View File

@@ -7,34 +7,20 @@
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 { usePlayerSelector } from "../../utils/PlayerExternalStore";
import { PlayerObject } from "src/PersonObjects/Player/PlayerObject";
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;
}
const selectHacknetExpenses = (p: PlayerObject) => -p.moneySourceA.hacknet_expenses || 0;
const selectHacknetMoney = (p: PlayerObject) => p.moneySourceA.hacknet;
function HacknetExpenses(): React.ReactElement {
const spent = usePlayerSelector(selectHacknetExpenses);
return <Money key="money" money={spent} />;
}
function HacknetProduced(): React.ReactElement {
const produced = usePlayerSelector(selectHacknetMoney);
return <Money key="money" money={produced} />;
}
export function PlayerInfo(props: IProps): React.ReactElement {
const hasServers = hasHacknetServers();
@@ -45,7 +31,7 @@ export function PlayerInfo(props: IProps): React.ReactElement {
rows.push([
"Hashes:",
<span key={"hashes"}>
<Hashes hashes={Player.hashManager.hashes} /> / <Hashes hashes={Player.hashManager.capacity} />
<PlayerHashes /> / <PlayerHashCapacity />
</span>,
]);
rows.push([
@@ -59,7 +45,7 @@ export function PlayerInfo(props: IProps): React.ReactElement {
}
>
<span>
<HashRate key="hashRate" hashes={props.totalProduction} />
<HashTotalProduction />
</span>
</Tooltip>,
]);

View File

@@ -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 -&nbsp;
<Money money={cost} forPurchase={true} />
<MoneyCost cost={cost} />
</>
);
}
@@ -31,14 +29,14 @@ export function PurchaseButton(props: IProps): React.ReactElement {
text = (
<>
Purchase Hacknet Node -&nbsp;
<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
View 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;
}