Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@mantine/core": "8.3.15",
"@mantine/hooks": "8.3.15",
"@mantine/notifications": "8.3.15",
"break_infinity.js": "^2.2.0",
"react": "19.2.4",
"react-dom": "19.2.4",
"zustand": "5.0.11"
Expand Down
9 changes: 6 additions & 3 deletions src/components/PetDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,10 @@ export function PetDisplay() {
}}
/>
)}
<FloatingParticles particles={particles} clickPower={baseClickPower} />
<FloatingParticles
particles={particles}
clickPower={baseClickPower.toNumber()}
/>
<Stack align="center" justify="center" gap="lg" h="100%">
<SpeechBubble text={dialogueLine} />
<Text size="xs" ff="monospace" c="dimmed">
Expand Down Expand Up @@ -316,7 +319,7 @@ export function PetDisplay() {
performRebirth(selectedSpecies, challengeId);
setRebirthModalOpen(false);
}}
totalTdEarned={totalTdEarned}
totalTdEarned={totalTdEarned.toNumber()}
currentBalance={prestigeTokenBalance}
nextSpecies={nextSpecies}
currentSpecies={currentSpecies}
Expand All @@ -326,7 +329,7 @@ export function PetDisplay() {
activeChallengeId={activeChallengeId}
totalClicks={totalClicks}
evolutionStage={evolutionStage}
peakTdPerSecond={peakTdPerSecond}
peakTdPerSecond={peakTdPerSecond.toNumber()}
runStart={runStart}
/>
<PrestigeShop opened={shopOpen} onClose={() => setShopOpen(false)} />
Expand Down
14 changes: 8 additions & 6 deletions src/components/StatsBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { useInterpolatedTd } from "../hooks/useInterpolatedTd";
import { useGameStore } from "../store";
import { useSettingsStore } from "../store/settingsStore";
import { D, type Decimal } from "../utils/decimal";
import { formatNumber, formatNumberFull } from "../utils/formatNumber";

const RATE_BOOST_DURATION_MS = 3000;
Expand Down Expand Up @@ -45,7 +46,8 @@ export function StatsBar() {
idleBoost * speciesBonus.autoGen,
boosterMultiplier,
);
const tdPerSecond = activeChallengeId === "click-only" ? 0 : rawTdPerSecond;
const tdPerSecond =
activeChallengeId === "click-only" ? D(0) : rawTdPerSecond;
const clickMastery = getClickMasteryBonus(ep["click-mastery"] ?? 0);
const effectiveClickPower = computeClickPower(
{ clickUpgradesPurchased, comboCount, lastClickTime },
Expand All @@ -59,11 +61,11 @@ export function StatsBar() {
const fmt = numberFormat === "full" ? formatNumberFull : formatNumber;

// Rate-of-change indicator: show sparkle when TD/s increases
const prevTdPerSecondRef = useRef(tdPerSecond);
const prevTdPerSecondRef = useRef<Decimal>(tdPerSecond);
const [rateBoosted, setRateBoosted] = useState(false);

useEffect(() => {
if (tdPerSecond > prevTdPerSecondRef.current) {
if (tdPerSecond.gt(prevTdPerSecondRef.current)) {
setRateBoosted(true);
const timer = setTimeout(
() => setRateBoosted(false),
Expand Down Expand Up @@ -92,8 +94,8 @@ export function StatsBar() {
</Text>
<Text size="sm" ff="monospace">
TD/s:{" "}
<Text span fw={700} c={tdPerSecond > 0 ? "green" : "dimmed"}>
{tdPerSecond > 0 ? fmt(tdPerSecond) : "0.0"}
<Text span fw={700} c={tdPerSecond.gt(0) ? "green" : "dimmed"}>
{tdPerSecond.gt(0) ? fmt(tdPerSecond) : "0.0"}
</Text>
{rateBoosted && (
<Text
Expand All @@ -113,7 +115,7 @@ export function StatsBar() {
<Text size="sm" ff="monospace">
Click:{" "}
<Text span fw={700} c="cyan">
{fmt(Math.floor(effectiveClickPower))} TD
{fmt(effectiveClickPower)} TD
</Text>
</Text>
{rebirthCount > 0 && (
Expand Down
11 changes: 6 additions & 5 deletions src/components/StatsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Badge, Divider, Group, Modal, Stack, Text } from "@mantine/core";
import { ACHIEVEMENTS } from "../data/achievements";
import { useGameStore } from "../store";
import { Decimal } from "../utils/decimal";
import { formatNumber } from "../utils/formatNumber";

function formatTime(totalSeconds: number): string {
Expand Down Expand Up @@ -74,9 +75,9 @@ export function StatsPanel({
0,
);

const isBestRun = rebirthCount > 0 && totalTdEarned > lifetimeBestRunTd;
const isBestRun = rebirthCount > 0 && totalTdEarned.gt(lifetimeBestRunTd);
const isPeakTdPs =
rebirthCount > 0 && peakTdPerSecond > lifetimePeakTdPerSecond;
rebirthCount > 0 && peakTdPerSecond.gt(lifetimePeakTdPerSecond);

return (
<Modal
Expand Down Expand Up @@ -129,7 +130,7 @@ export function StatsPanel({
<Stack gap={2}>
<StatRow
label="Total TD (all runs)"
value={formatNumber(lifetimeTdEarned + totalTdEarned)}
value={formatNumber(lifetimeTdEarned.add(totalTdEarned))}
/>
<StatRow label="Rebirths" value={rebirthCount.toLocaleString()} />
<StatRow
Expand All @@ -138,11 +139,11 @@ export function StatsPanel({
/>
<StatRow
label="Best run TD"
value={formatNumber(Math.max(lifetimeBestRunTd, totalTdEarned))}
value={formatNumber(Decimal.max(lifetimeBestRunTd, totalTdEarned))}
/>
<StatRow
label="All-time peak TD/s"
value={`${formatNumber(Math.max(lifetimePeakTdPerSecond, peakTdPerSecond))}/s`}
value={`${formatNumber(Decimal.max(lifetimePeakTdPerSecond, peakTdPerSecond))}/s`}
/>
<StatRow
label="Wisdom earned (total)"
Expand Down
5 changes: 3 additions & 2 deletions src/components/UpgradesSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useSound } from "../hooks/useSound";
import { useGameStore } from "../store";
import type { BuyMode } from "../store/settingsStore";
import { useSettingsStore } from "../store/settingsStore";
import { D } from "../utils/decimal";
import { ClickUpgradeCard } from "./upgrades/ClickUpgradeCard";
import { UpgradeCard } from "./upgrades/UpgradeCard";

Expand Down Expand Up @@ -108,7 +109,7 @@ export function UpgradesSidebar() {
(id: string, qty: number) => {
const dataBefore = useGameStore.getState().trainingData;
purchaseBulkUpgrade(id, qty);
if (useGameStore.getState().trainingData !== dataBefore) {
if (!D(useGameStore.getState().trainingData).eq(dataBefore)) {
playPurchase();
}
},
Expand All @@ -119,7 +120,7 @@ export function UpgradesSidebar() {
(id: string) => {
const dataBefore = useGameStore.getState().trainingData;
purchaseClickUpgrade(id);
if (useGameStore.getState().trainingData !== dataBefore) {
if (!D(useGameStore.getState().trainingData).eq(dataBefore)) {
playPurchase();
}
},
Expand Down
6 changes: 4 additions & 2 deletions src/components/upgrades/BoosterCard.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { Badge, Button, Card, Group, Text } from "@mantine/core";
import { notifications } from "@mantine/notifications";
import type { DecimalSource } from "break_infinity.js";
import { useCallback, useRef, useState } from "react";
import type { Booster } from "../../data/boosters";
import { useReducedMotion } from "../../hooks/useReducedMotion";
import { D } from "../../utils/decimal";
import { formatNumber } from "../../utils/formatNumber";

interface BoosterCardProps {
booster: Booster;
purchased: boolean;
trainingData: number;
trainingData: DecimalSource;
evolutionStage: number;
onPurchase: (id: string) => void;
}
Expand All @@ -20,7 +22,7 @@ export function BoosterCard({
evolutionStage,
onPurchase,
}: BoosterCardProps) {
const canAfford = !purchased && trainingData >= booster.cost;
const canAfford = !purchased && D(trainingData).gte(booster.cost);
const locked = evolutionStage < booster.unlockStage;
const [isGlowing, setIsGlowing] = useState(false);
const glowTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
Expand Down
6 changes: 4 additions & 2 deletions src/components/upgrades/ClickUpgradeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ import {
Popover,
Text,
} from "@mantine/core";
import type { DecimalSource } from "break_infinity.js";
import { useCallback, useRef, useState } from "react";
import type { ClickUpgrade } from "../../data/clickUpgrades";
import { useReducedMotion } from "../../hooks/useReducedMotion";
import { D } from "../../utils/decimal";
import { formatNumber } from "../../utils/formatNumber";
import { ClickUpgradeTooltipContent } from "./ClickUpgradeTooltipContent";

interface ClickUpgradeCardProps {
upgrade: ClickUpgrade;
purchased: boolean;
trainingData: number;
trainingData: DecimalSource;
evolutionStage: number;
onPurchase: (id: string) => void;
}
Expand All @@ -28,7 +30,7 @@ export function ClickUpgradeCard({
evolutionStage,
onPurchase,
}: ClickUpgradeCardProps) {
const canAfford = !purchased && trainingData >= upgrade.cost;
const canAfford = !purchased && D(trainingData).gte(upgrade.cost);
const locked = evolutionStage < upgrade.unlockStage;
const [isGlowing, setIsGlowing] = useState(false);
const [tooltipOpen, setTooltipOpen] = useState(false);
Expand Down
6 changes: 4 additions & 2 deletions src/components/upgrades/UpgradeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Text,
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
import type { DecimalSource } from "break_infinity.js";
import { useCallback, useEffect, useRef, useState } from "react";
import { MILESTONE_THRESHOLDS } from "../../data/milestones";
import { SYNERGIES } from "../../data/synergies";
Expand All @@ -20,14 +21,15 @@ import { getSynergyMultiplier } from "../../engine/synergyEngine";
import { getBulkCost, getMaxAffordable } from "../../engine/upgradeEngine";
import { useReducedMotion } from "../../hooks/useReducedMotion";
import type { BuyMode } from "../../store/settingsStore";
import { D } from "../../utils/decimal";
import { formatNumber } from "../../utils/formatNumber";
import { GeneratorTooltipContent } from "./GeneratorTooltipContent";

interface UpgradeCardProps {
upgrade: Upgrade;
owned: number;
allOwned?: Record<string, number>;
trainingData: number;
trainingData: DecimalSource;
buyMode: BuyMode;
onPurchase: (id: string, count: number) => void;
costMultiplier?: number;
Expand All @@ -47,7 +49,7 @@ export function UpgradeCard({
? getMaxAffordable(upgrade, owned, trainingData, costMultiplier)
: buyMode;
const cost = getBulkCost(upgrade, owned, count, costMultiplier);
const canAfford = count > 0 && trainingData >= cost;
const canAfford = count > 0 && D(trainingData).gte(cost);

const milestoneLevel = getMilestoneLevel(owned);
const milestoneMultiplier = getMilestoneMultiplier(owned);
Expand Down
5 changes: 3 additions & 2 deletions src/components/upgrades/tooltipHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ export function computeGeneratorTooltipData(
// cancel out in the percentage calculation — the % share is the same
// regardless of which global multipliers are active.
const grandTotal = getTotalTdPerSecond(UPGRADES, allOwned, 1, 1);
const percentOfTotal =
grandTotal > 0 ? (totalTdForGenerator / grandTotal) * 100 : 0;
const percentOfTotal = grandTotal.gt(0)
? (totalTdForGenerator / grandTotal.toNumber()) * 100
: 0;

const nextThreshold = MILESTONE_THRESHOLDS[milestoneLevel] ?? null;

Expand Down
19 changes: 11 additions & 8 deletions src/data/achievements.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { describe, expect, it } from "vitest";
import type { GameState } from "../store/gameStore";
import { D } from "../utils/decimal";
import { ACHIEVEMENTS } from "./achievements";

const emptyState: GameState = {
trainingData: 0,
trainingData: D(0),
totalClicks: 0,
totalTdEarned: 0,
totalTdEarned: D(0),
evolutionStage: 0,
lastSaved: 0,
upgradeOwned: {},
Expand All @@ -29,11 +30,11 @@ const emptyState: GameState = {
prestigeTokenBalance: 0,
hasOpenedPrestigeShop: false,
runStart: 0,
peakTdPerSecond: 0,
peakTdPerSecond: D(0),
peakGeneratorsOwned: 0,
lifetimeTdEarned: 0,
lifetimePeakTdPerSecond: 0,
lifetimeBestRunTd: 0,
lifetimeTdEarned: D(0),
lifetimePeakTdPerSecond: D(0),
lifetimeBestRunTd: D(0),
lifetimeWisdomEarned: 0,
activeChallengeId: null,
};
Expand Down Expand Up @@ -113,10 +114,12 @@ describe("ACHIEVEMENTS", () => {
it("td-1m fires when totalTdEarned >= 1_000_000", () => {
const a = ACHIEVEMENTS.find((x) => x.id === "td-1m");
expect(a).toBeDefined();
expect(a?.condition({ ...emptyState, totalTdEarned: 1_000_000 })).toBe(
expect(a?.condition({ ...emptyState, totalTdEarned: D(1_000_000) })).toBe(
true,
);
expect(a?.condition({ ...emptyState, totalTdEarned: 999_999 })).toBe(false);
expect(a?.condition({ ...emptyState, totalTdEarned: D(999_999) })).toBe(
false,
);
});

it("first-rebirth fires when rebirthCount >= 1", () => {
Expand Down
8 changes: 4 additions & 4 deletions src/data/achievements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,19 @@ export const ACHIEVEMENTS: readonly Achievement[] = [
id: "td-1k",
name: "Data Hoarder",
description: "Earn 1,000 TD total",
condition: (s) => s.totalTdEarned >= 1_000,
condition: (s) => s.totalTdEarned.gte(1_000),
},
{
id: "td-1m",
name: "Big Data",
description: "Earn 1,000,000 TD total",
condition: (s) => s.totalTdEarned >= 1_000_000,
condition: (s) => s.totalTdEarned.gte(1_000_000),
},
{
id: "td-1b",
name: "Data Singularity",
description: "Earn 1,000,000,000 TD total",
condition: (s) => s.totalTdEarned >= 1_000_000_000,
condition: (s) => s.totalTdEarned.gte(1_000_000_000),
},
{
id: "first-rebirth",
Expand Down Expand Up @@ -164,6 +164,6 @@ export const ACHIEVEMENTS: readonly Achievement[] = [
id: "td-1t",
name: "Trillionaire",
description: "A trillion training data points. GLORP transcends.",
condition: (s) => s.totalTdEarned >= 1_000_000_000_000,
condition: (s) => s.totalTdEarned.gte(1_000_000_000_000),
},
];
Loading
Loading