forked from mico/idle_moloch
270 lines
7.2 KiB
TypeScript
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;
|