Compare commits

..

No commits in common. "5491598dfc6a653144b766f4a826ef5add013e5b" and "fefa2641e4cca44c303da082f90be3175e546465" have entirely different histories.

7 changed files with 50 additions and 88 deletions

View File

@ -1,27 +1,17 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { UnitType, usePlayer } from '../providers/PlayerProvider'; import { UnitType, usePlayer } from '../providers/PlayerProvider';
import styles from '../styles/Army.module.css'; import styles from '../styles/Army.module.css';
import { calculateBalance, toReadable } from './Counter'; import { toReadable } from './Counter';
const PRECISION = BigInt(10000); const PRECISION = BigInt(10000);
const BASE_PRICE = BigInt(380000);
const PRICE_FACTOR = BigInt(11500); const PRICE_FACTOR = BigInt(11500);
const APPRENTICE_PROFIT = BigInt(61000);
const base_cost: Record<UnitType, bigint> = { const ANOINTED_PROFIT = BigInt(6 * 64000);
0: BigInt(380000), const CHAMPION_PROFIT = BigInt(BigInt(67000 * 61000 * 64000) / PRECISION / PRECISION);
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) { function calculateUnitPrice(unit: UnitType, currentLevel: number, units: number) {
let rollingPriceCalculation = base_cost[unit]; let rollingPriceCalculation = BigInt(unit + 1) * BASE_PRICE;
let price = BigInt(0); let price = BigInt(0);
// Each level costs 15% more than previous // Each level costs 15% more than previous
@ -36,7 +26,12 @@ function calculateUnitPrice(unit: UnitType, currentLevel: number, units: number)
function calculateProfitPerSecond(unit: UnitType, level: number) { function calculateProfitPerSecond(unit: UnitType, level: number) {
// Each next unit scales progressivelly better // Each next unit scales progressivelly better
return BigInt(level) * profits[unit] switch (unit) {
case 0: return BigInt(level) * PRECISION
case 1: return BigInt(level) * APPRENTICE_PROFIT
case 2: return BigInt(level) * ANOINTED_PROFIT
case 3: return BigInt(level) * CHAMPION_PROFIT
}
} }
const unitTypeToCss: Record<UnitType, string> = { const unitTypeToCss: Record<UnitType, string> = {
@ -62,15 +57,15 @@ const defaultAvailabilityMap: Record<UnitType, boolean> = {
const unitDiscoveredAt: Record<UnitType, bigint> = { const unitDiscoveredAt: Record<UnitType, bigint> = {
0: BigInt(0), 0: BigInt(0),
1: BigInt(300_0000), 1: BigInt(2_000_0000),
2: BigInt(2800_0000), 2: BigInt(2_000_000_0000),
3: BigInt(24000_0000) 3: BigInt(200_000_000_0000)
} }
const unitAvailableToDiscoverAt: Record<UnitType, bigint> = { const unitAvailableToDiscoverAt: Record<UnitType, bigint> = {
0: BigInt(0), 0: BigInt(0),
1: BigInt(200_0000), 1: BigInt(1_000_0000),
2: BigInt(2000_0000), 2: BigInt(1_000_000_0000),
3: BigInt(25000_0000), 3: BigInt(100_000_000_0000),
} }
interface UnitProps { interface UnitProps {
@ -84,8 +79,8 @@ interface UnitProps {
const Unit = ({ addUnit, unitType, canPurchase, isShrouded, n_units }: UnitProps) => { const Unit = ({ addUnit, unitType, canPurchase, isShrouded, n_units }: UnitProps) => {
const [unitPrice, unitProfit] = useMemo(() => { const [unitPrice, unitProfit] = useMemo(() => {
return [ return [
toReadable(calculateUnitPrice(unitType, n_units, 1)), toReadable(calculateUnitPrice(unitType, n_units, 1)) + " geld",
toReadable(calculateProfitPerSecond(unitType, n_units)) toReadable(calculateProfitPerSecond(unitType, n_units)) + " geld / sec"
] ]
}, [n_units, unitType]); }, [n_units, unitType]);
return <div onClick={() => addUnit(unitType)} className={`${styles.armyUnit} ${canPurchase ? "" : styles.isUnavailable}`}> return <div onClick={() => addUnit(unitType)} className={`${styles.armyUnit} ${canPurchase ? "" : styles.isUnavailable}`}>
@ -96,9 +91,9 @@ const Unit = ({ addUnit, unitType, canPurchase, isShrouded, n_units }: UnitProps
${isShrouded ? styles.isShrouded : ""} ${isShrouded ? styles.isShrouded : ""}
`} /> `} />
<span className={`${styles.unitName} ${styles.uiElement}`}>{isShrouded ? "???????" : unitTypeToName[unitType]}</span> <span className={`${styles.unitName} ${styles.uiElement}`}>{isShrouded ? "???????" : unitTypeToName[unitType]}</span>
{isShrouded ? null : <span className={`${styles.unitPrice} ${styles.uiElement}`}>{unitPrice} <small>GELD</small></span>} {isShrouded ? null : <span className={`${styles.unitPrice} ${styles.uiElement}`}>{unitPrice}</span>}
{n_units > 0 ? <span className={`${styles.unitSupply} ${styles.uiElement}`}>{n_units}</span> : null} {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} {n_units > 0 ? <span className={`${styles.unitProfit} ${styles.uiElement}`}>{unitProfit}</span> : null}
</div> </div>
} }
@ -107,11 +102,12 @@ const Army = () => {
const [canPurchase, setCanPurchase] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap); const [canPurchase, setCanPurchase] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap);
const [isShrouded, setIsShrouded] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap); const [isShrouded, setIsShrouded] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap);
const [canKnowAbout, setCanKnowAbout] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap); const [canKnowAbout, setCanKnowAbout] = useState<Record<UnitType, boolean>>(defaultAvailabilityMap);
const balanceCount = useRef(BigInt(balance ?? 0))
const setAvailabilities = useCallback(() => { console.log(canPurchase, "\nshroud", isShrouded, "\ncanknowabout", canKnowAbout)
useEffect(() => {
if (isRegistered) { if (isRegistered) {
const totalMinted = (player?.total_minted ?? BigInt(0)) + (balanceCount.current - (balance ?? BigInt(0))); const totalMinted = player?.total_minted ?? BigInt(0);
const n_units: Record<UnitType, number> = { const n_units: Record<UnitType, number> = {
0: army?.moloch_denier.level ?? 0, 0: army?.moloch_denier.level ?? 0,
1: army?.apprentice.level ?? 0, 1: army?.apprentice.level ?? 0,
@ -131,10 +127,10 @@ const Army = () => {
3: totalMinted >= unitAvailableToDiscoverAt[3], 3: totalMinted >= unitAvailableToDiscoverAt[3],
} }
const canActuallyBuy = { const canActuallyBuy = {
0: balanceCount.current >= calculateUnitPrice(0, n_units[0], 1) && isKnown[0] && !inShroud[0], 0: balance >= calculateUnitPrice(0, n_units[0], 1) && isKnown[0] && !inShroud[0],
1: balanceCount.current >= calculateUnitPrice(1, n_units[1], 1) && isKnown[1] && !inShroud[1], 1: balance >= calculateUnitPrice(1, n_units[1], 1) && isKnown[1] && !inShroud[1],
2: balanceCount.current >= calculateUnitPrice(2, n_units[2], 1) && isKnown[2] && !inShroud[2], 2: balance >= calculateUnitPrice(2, n_units[2], 1) && isKnown[2] && !inShroud[2],
3: balanceCount.current >= calculateUnitPrice(3, n_units[3], 1) && isKnown[3] && !inShroud[3], 3: balance >= calculateUnitPrice(3, n_units[3], 1) && isKnown[3] && !inShroud[3],
}; };
setCanPurchase(canActuallyBuy) setCanPurchase(canActuallyBuy)
setIsShrouded(inShroud) setIsShrouded(inShroud)
@ -145,24 +141,12 @@ const Army = () => {
setCanKnowAbout(defaultAvailabilityMap) setCanKnowAbout(defaultAvailabilityMap)
} }
}, [ }, [
army,
balance, balance,
army,
isRegistered, isRegistered,
player?.total_minted 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 <> return <>
<div className={`${styles.tavern_keeper} ${styles.person} ${styles.static}`} /> <div className={`${styles.tavern_keeper} ${styles.person} ${styles.static}`} />
<div className={styles.armyUnits}> <div className={styles.armyUnits}>

View File

@ -1,9 +1,8 @@
import { useEffect, useReducer, useRef } from "react"; import { useEffect, useReducer, useRef } from "react";
import { usePlayer } from "../providers/PlayerProvider" import { usePlayer } from "../providers/PlayerProvider"
import styles from "../styles/Header.module.css" import styles from "../styles/Header.module.css"
import { formatUnits } from "viem";
export const calculateBalance = (balance: bigint, perSecond: bigint, lastRaidedAt: bigint) => { const calculateBalance = (balance: bigint, perSecond: bigint, lastRaidedAt: bigint) => {
// convert to milliseconds trick so we get a more smooth counter // convert to milliseconds trick so we get a more smooth counter
const millisecondsSinceLastRaid = const millisecondsSinceLastRaid =
(new Date()).getTime() - parseInt(lastRaidedAt.toString()) * 1000; (new Date()).getTime() - parseInt(lastRaidedAt.toString()) * 1000;
@ -13,8 +12,8 @@ export const calculateBalance = (balance: bigint, perSecond: bigint, lastRaidedA
/ BigInt(1000) /* deduct milliseconds*/)) / BigInt(1000) /* deduct milliseconds*/))
} }
export const toReadable = (rawValue: bigint) => { export const toReadable = (value: bigint) => {
const value = rawValue / BigInt(10000); value = value / BigInt(10000);
const suffixes = [ const suffixes = [
{ value: BigInt('1000'), suffix: 'thousand' }, { value: BigInt('1000'), suffix: 'thousand' },
{ value: BigInt('1000000'), suffix: 'million' }, { value: BigInt('1000000'), suffix: 'million' },
@ -39,7 +38,7 @@ export const toReadable = (rawValue: bigint) => {
for (let i = 0; i < suffixes.length; i++) { for (let i = 0; i < suffixes.length; i++) {
if (value < suffixes[i].value) { if (value < suffixes[i].value) {
if (i == 0) { if (i == 0) {
return formatUnits(rawValue, 4); return value.toString();
} else { } else {
const divided = value / suffixes[i - 1].value; const divided = value / suffixes[i - 1].value;
const numStr = (value % suffixes[i - 1].value).toString().slice(0, 3); const numStr = (value % suffixes[i - 1].value).toString().slice(0, 3);

View File

@ -145,11 +145,6 @@
filter: sepia(1); filter: sepia(1);
pointer-events: none; pointer-events: none;
} }
&:hover {
.unitProfit {
display: block;
}
}
} }
.uiElement { .uiElement {
position: absolute; position: absolute;
@ -178,7 +173,6 @@
left: 0; left: 0;
right: 0; right: 0;
bottom: 5px; bottom: 5px;
display: none;
} }
.static { .static {
width: 110px; width: 110px;

View File

@ -61,13 +61,14 @@ contract RaidGeld is ERC20, Ownable {
// Helper so we can use it when buying units too // Helper so we can use it when buying units too
function performRaid(address player) private { function performRaid(address player) private {
uint256 time_past = block.timestamp - players[player].last_raided_at; uint256 time_past = block.timestamp - players[player].last_raided_at;
uint256 new_geld = armies[player].profit_per_second * time_past; uint256 new_geld = armies[player].profit_per_second * time_past;
// TODO: Pink noise, make it so sometimes its better than expected // TODO: Pink noise, make it so sometimes its better than expected
_mint(player, new_geld); _mint(player, new_geld);
players[player].last_raided_at = block.timestamp; players[player].last_raided_at = block.timestamp;
players[player].total_minted += new_geld; players[player].total_minted = new_geld;
} }
// Function to get Player struct // Function to get Player struct
@ -107,11 +108,8 @@ contract RaidGeld is ERC20, Ownable {
uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units); uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units);
// First trigger a raid so player receives what he is due at to this moment // First trigger a raid so player receives what he is due at to this moment
uint256 time_past = block.timestamp - players[msg.sender].last_raided_at;
uint256 new_geld = armies[msg.sender].profit_per_second * time_past;
require(balanceOf(msg.sender) + new_geld > cost, "Not enough GELD to add this unit");
performRaid(msg.sender); performRaid(msg.sender);
require(balanceOf(msg.sender) > cost, "Not enough GELD to add this much");
// TODO: Since we are first minting then burning the token, this could be simplified // TODO: Since we are first minting then burning the token, this could be simplified
// by first calculating the difference and then minting / burning in just one operation // by first calculating the difference and then minting / burning in just one operation

View File

@ -5,31 +5,18 @@ import {Army} from "../src/RaidGeldStructs.sol";
library RaidGeldUtils { library RaidGeldUtils {
uint256 public constant PRECISION = 10000; uint256 public constant PRECISION = 10000;
uint256 constant BASE_PRICE = 380000;
uint256 constant PRICE_FACTOR = 11500; uint256 constant PRICE_FACTOR = 11500;
uint256 constant MOLOCH_DENIER_PROFIT = 10000;
// base price * (0.00666) * 11 per each next unit uint256 constant APPRENTICE_PROFIT = 61000;
uint256 constant MOLOCH_DENIER_PROFIT = 2533; uint256 constant ANOINTED_PROFIT = 6 * 64000;
uint256 constant APPRENTICE_PROFIT = 27863; uint256 constant CHAMPION_PROFIT = 36 * 67000;
uint256 constant ANOINTED_PROFIT = 306493;
uint256 constant CHAMPION_PROFIT = 3371423;
// each costs 10 times plus a bit more
uint256 constant MOLOCH_DENIER_BASE_COST = 380000;
uint256 constant APPRENTICE_BASE_COST = 3420000;
uint256 constant ANOINTED_BASE_COST = 30096000;
uint256 constant CHAMPION_BASE_COST = 255816000;
function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) { function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) {
require(unit <= 3, "No matching unit found"); require(unit <= 3, "No matching unit found");
uint256 rollingPriceCalculation = MOLOCH_DENIER_BASE_COST;
uint256 rollingPriceCalculation = uint256(unit + 1) * BASE_PRICE;
uint256 price = 0; uint256 price = 0;
if (unit == 1) {
rollingPriceCalculation = APPRENTICE_BASE_COST;
} else if (unit == 2) {
rollingPriceCalculation = ANOINTED_BASE_COST;
} else if (unit == 3) {
rollingPriceCalculation = CHAMPION_BASE_COST;
}
// Each level costs 15% more than previous // Each level costs 15% more than previous
for (uint256 i = 0; i < currentLevel + units; i++) { for (uint256 i = 0; i < currentLevel + units; i++) {

View File

@ -9,7 +9,7 @@ contract raid_geldTest is Test {
function test_0_unit_price() public pure { function test_0_unit_price() public pure {
// buying 1 unit of moloch_denier // buying 1 unit of moloch_denier
uint256 basePriceMolochDenier = RaidGeldUtils.calculateUnitPrice(0, 0, 1); uint256 basePriceMolochDenier = RaidGeldUtils.calculateUnitPrice(0, 0, 1);
assertEq(basePriceMolochDenier, RaidGeldUtils.MOLOCH_DENIER_BASE_COST); assertEq(basePriceMolochDenier, RaidGeldUtils.BASE_PRICE);
// buying 3 units // buying 3 units
// has to be a bit more than 3 * 38 = 114 // has to be a bit more than 3 * 38 = 114
@ -37,7 +37,7 @@ contract raid_geldTest is Test {
profit_per_second: 0 // irrelevant for this test profit_per_second: 0 // irrelevant for this test
}); });
uint256 profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army); uint256 profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
assertEq(profits_per_second, RaidGeldUtils.MOLOCH_DENIER_PROFIT); assertEq(profits_per_second, RaidGeldUtils.PRECISION);
army = Army({ army = Army({
moloch_denier: Raider({level: _dLvl}), moloch_denier: Raider({level: _dLvl}),
@ -47,7 +47,7 @@ contract raid_geldTest is Test {
profit_per_second: 0 // irrelevant for this test profit_per_second: 0 // irrelevant for this test
}); });
profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army); profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
uint256 expected = _dLvl * RaidGeldUtils.MOLOCH_DENIER_PROFIT + _apLvl * RaidGeldUtils.APPRENTICE_PROFIT uint256 expected = _dLvl * RaidGeldUtils.PRECISION + _apLvl * RaidGeldUtils.APPRENTICE_PROFIT
+ _anLvl * RaidGeldUtils.ANOINTED_PROFIT + _cLvl * RaidGeldUtils.CHAMPION_PROFIT; + _anLvl * RaidGeldUtils.ANOINTED_PROFIT + _cLvl * RaidGeldUtils.CHAMPION_PROFIT;
assertEq(profits_per_second, expected); assertEq(profits_per_second, expected);
} }