1
0
forked from mico/idle_moloch
idle_moloch/app/src/components/Army.tsx

270 lines
7.2 KiB
TypeScript

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { UnitType, usePlayer } from "../providers/PlayerProvider";
import styles from "../styles/Army.module.css";
import { calculateBalance, toReadable } from "./Counter";
import PixelatedQuote from "./PixelatedQuote";
const PRECISION = BigInt(10000);
const PRICE_FACTOR = BigInt(11500);
const base_cost: Record<UnitType, bigint> = {
0: BigInt(380000),
1: BigInt(3420000),
2: BigInt(30096000),
3: BigInt(255816000),
};
const profits: Record<UnitType, bigint> = {
0: BigInt(2533),
1: BigInt(27863),
2: BigInt(306493),
3: BigInt(3371423),
};
function calculateUnitPrice(
unit: UnitType,
currentLevel: number,
units: number
) {
let rollingPriceCalculation = base_cost[unit];
let price = BigInt(0);
// Each level costs 15% more than previous
for (let i = 0; i < currentLevel + units; i++) {
if (i >= currentLevel) {
price += rollingPriceCalculation;
}
rollingPriceCalculation =
(rollingPriceCalculation * PRICE_FACTOR) / PRECISION;
}
return price;
}
function calculateProfitPerSecond(unit: UnitType, level: number) {
// Each next unit scales progressivelly better
return BigInt(level) * profits[unit];
}
const unitTypeToCss: Record<UnitType, string> = {
0: styles.moloch_denier,
1: styles.apprentice,
2: styles.anointed,
3: styles.champion,
};
const unitTypeToName: Record<UnitType, string> = {
0: "Moloch denier",
1: "Apprentice",
2: "Anointed",
3: "Champion",
};
const defaultAvailabilityMap: Record<UnitType, boolean> = {
0: false,
1: false,
2: false,
3: false,
};
const unitDiscoveredAt: Record<UnitType, bigint> = {
0: BigInt(0),
1: BigInt(300_0000),
2: BigInt(2800_0000),
3: BigInt(24000_0000),
};
const unitAvailableToDiscoverAt: Record<UnitType, bigint> = {
0: BigInt(0),
1: BigInt(200_0000),
2: BigInt(2000_0000),
3: BigInt(25000_0000),
};
interface UnitProps {
addUnit: (unitType: UnitType) => void;
unitType: UnitType;
canPurchase: boolean;
isShrouded: boolean;
n_units: number;
}
const Unit = ({
addUnit,
unitType,
canPurchase,
isShrouded,
n_units,
}: UnitProps) => {
const [unitPrice, unitProfit] = useMemo(() => {
return [
toReadable(calculateUnitPrice(unitType, n_units, 1)),
toReadable(calculateProfitPerSecond(unitType, n_units)),
];
}, [n_units, unitType]);
return (
<div
onClick={() => addUnit(unitType)}
className={`${styles.armyUnit} ${canPurchase ? "" : styles.isUnavailable
}`}
>
<div
className={`
${unitTypeToCss[unitType]}
${styles.person}
${styles.static}
${isShrouded ? styles.isShrouded : ""}
`}
/>
<span className={`${styles.unitName} ${styles.uiElement}`}>
{isShrouded ? "???????" : unitTypeToName[unitType]}
</span>
{isShrouded ? null : (
<span className={`${styles.unitPrice} ${styles.uiElement}`}>
{unitPrice} <small>GELD</small>
</span>
)}
{n_units > 0 ? (
<span className={`${styles.unitSupply} ${styles.uiElement}`}>
{n_units}
</span>
) : null}
{n_units > 0 ? (
<span className={`${styles.unitProfit} ${styles.uiElement}`}>
{unitProfit} <small>per sec</small>
</span>
) : null}
</div>
);
};
const Army = () => {
const { army, addUnit, isRegistered, player, balance } = usePlayer();
const [canPurchase, setCanPurchase] = useState<Record<UnitType, boolean>>(
defaultAvailabilityMap
);
const [isShrouded, setIsShrouded] = useState<Record<UnitType, boolean>>(
defaultAvailabilityMap
);
const [canKnowAbout, setCanKnowAbout] = useState<Record<UnitType, boolean>>(
defaultAvailabilityMap
);
const balanceCount = useRef(BigInt(balance ?? 0));
const setAvailabilities = useCallback(() => {
if (isRegistered) {
const totalMinted =
(player?.total_minted ?? BigInt(0)) +
(balanceCount.current - (balance ?? BigInt(0)));
const n_units: Record<UnitType, number> = {
0: army?.moloch_denier.level ?? 0,
1: army?.apprentice.level ?? 0,
2: army?.anointed.level ?? 0,
3: army?.champion.level ?? 0,
};
const inShroud = {
0: totalMinted < unitDiscoveredAt[0],
1: totalMinted < unitDiscoveredAt[1],
2: totalMinted < unitDiscoveredAt[2],
3: totalMinted < unitDiscoveredAt[3],
};
const isKnown = {
0: totalMinted >= unitAvailableToDiscoverAt[0],
1: totalMinted >= unitAvailableToDiscoverAt[1],
2: totalMinted >= unitAvailableToDiscoverAt[2],
3: totalMinted >= unitAvailableToDiscoverAt[3],
};
const canActuallyBuy = {
0:
balanceCount.current >= calculateUnitPrice(0, n_units[0], 1) &&
isKnown[0] &&
!inShroud[0],
1:
balanceCount.current >= calculateUnitPrice(1, n_units[1], 1) &&
isKnown[1] &&
!inShroud[1],
2:
balanceCount.current >= calculateUnitPrice(2, n_units[2], 1) &&
isKnown[2] &&
!inShroud[2],
3:
balanceCount.current >= calculateUnitPrice(3, n_units[3], 1) &&
isKnown[3] &&
!inShroud[3],
};
setCanPurchase(canActuallyBuy);
setIsShrouded(inShroud);
setCanKnowAbout(isKnown);
} else {
setCanPurchase(defaultAvailabilityMap);
setIsShrouded(defaultAvailabilityMap);
setCanKnowAbout(defaultAvailabilityMap);
}
}, [army, balance, isRegistered, player?.total_minted]);
useEffect(() => {
const tickInterval = setInterval(() => {
balanceCount.current = calculateBalance(
balance ?? BigInt(0),
army?.profit_per_second ?? BigInt(0),
player?.last_raided_at ?? BigInt(0)
);
setAvailabilities();
}, 100);
return () => clearInterval(tickInterval);
}, [
balance,
army?.profit_per_second,
player?.last_raided_at,
setAvailabilities,
]);
return (
<>
<div
className={`${styles.tavern_keeper} ${styles.person} ${styles.static}`}
>
<PixelatedQuote />
</div>
<div className={styles.armyUnits}>
{canKnowAbout[0] && (
<Unit
n_units={army?.moloch_denier.level ?? 0}
addUnit={addUnit}
unitType={0}
canPurchase={canPurchase[0]}
isShrouded={isShrouded[0]}
/>
)}
{canKnowAbout[1] && (
<Unit
n_units={army?.apprentice.level ?? 0}
addUnit={addUnit}
unitType={1}
canPurchase={canPurchase[1]}
isShrouded={isShrouded[1]}
/>
)}
{canKnowAbout[2] && (
<Unit
n_units={army?.anointed.level ?? 0}
addUnit={addUnit}
unitType={2}
canPurchase={canPurchase[2]}
isShrouded={isShrouded[2]}
/>
)}
{canKnowAbout[3] && (
<Unit
n_units={army?.champion.level ?? 0}
addUnit={addUnit}
unitType={3}
canPurchase={canPurchase[3]}
isShrouded={isShrouded[3]}
/>
)}
</div>
</>
);
};
export default Army;