From c1e6494937270b12978abcab0bdd30a7ad41d0c9 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Sat, 26 Oct 2024 13:12:30 +0200 Subject: [PATCH] Added shrouding of units & display of per second profits on units plus fixed calc bug on unit costs --- app/src/components/Army.tsx | 173 ++++++++++++++++++++++++--- app/src/components/Counter.tsx | 2 +- app/src/components/Header.tsx | 3 +- app/src/providers/PlayerProvider.tsx | 8 +- app/src/styles/Army.module.css | 95 ++++++++++----- app/src/styles/Background.module.css | 11 +- src/RaidGeldUtils.sol | 14 +-- 7 files changed, 243 insertions(+), 63 deletions(-) diff --git a/app/src/components/Army.tsx b/app/src/components/Army.tsx index 888368d..4d7b0d0 100644 --- a/app/src/components/Army.tsx +++ b/app/src/components/Army.tsx @@ -1,26 +1,161 @@ -import { usePlayer } from '../providers/PlayerProvider'; +import { useEffect, useMemo, useState } from 'react'; +import { UnitType, usePlayer } from '../providers/PlayerProvider'; import styles from '../styles/Army.module.css'; +import { toReadable } from './Counter'; -const Army = () => { - const { army, addUnit, isRegistered } = usePlayer() - return
-
- {isRegistered && <> -
addUnit(0)} className={`${styles.scribe} ${styles.person} ${styles.moloch_denier} ${styles.static}`}> -
Moloch denier: {army?.moloch_denier.level}
-
-
addUnit(1)} className={`${styles.druid} ${styles.person} ${styles.apprentice} ${styles.static}`} > -
Apprentice: {army?.apprentice.level}
-
-
addUnit(2)} className={`${styles.ranger} ${styles.person} ${styles.anointed} ${styles.static}`} > -
Anointed: {army?.anointed.level}
-
-
addUnit(3)} className={`${styles.warrior} ${styles.person} ${styles.champion} ${styles.static}`} > -
Champion: {army?.champion.level}
-
+const PRECISION = BigInt(10000); +const BASE_PRICE = BigInt(380000); +const PRICE_FACTOR = BigInt(11500); +const APPRENTICE_PROFIT = BigInt(61000); +const ANOINTED_PROFIT = BigInt(6 * 64000); +const CHAMPION_PROFIT = BigInt(BigInt(67000 * 61000 * 64000) / PRECISION / PRECISION); - } +function calculateUnitPrice(unit: UnitType, currentLevel: number, units: number) { + let rollingPriceCalculation = BigInt(unit + 1) * BASE_PRICE; + 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 + 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 = { + 0: styles.moloch_denier, + 1: styles.apprentice, + 2: styles.anointed, + 3: styles.champion +} + +const unitTypeToName: Record = { + 0: "Moloch denier", + 1: "Apprentice", + 2: "Anointed", + 3: "Champion" +} + +const defaultAvailabilityMap: Record = { + 0: false, + 1: false, + 2: false, + 3: false +} + +const unitDiscoveredAt: Record = { + 0: BigInt(0), + 1: BigInt(2_000_0000), + 2: BigInt(2_000_000_0000), + 3: BigInt(200_000_000_0000) +} +const unitAvailableToDiscoverAt: Record = { + 0: BigInt(0), + 1: BigInt(1_000_0000), + 2: BigInt(1_000_000_0000), + 3: BigInt(100_000_000_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)) + " geld", + toReadable(calculateProfitPerSecond(unitType, n_units)) + " geld / sec" + ] + }, [n_units, unitType]); + return
addUnit(unitType)} className={`${styles.armyUnit} ${canPurchase ? "" : styles.isUnavailable}`}> +
+ {isShrouded ? "???????" : unitTypeToName[unitType]} + {isShrouded ? null : {unitPrice}} + {n_units > 0 ? {n_units} : null} + {n_units > 0 ? {unitProfit} : null}
} +const Army = () => { + const { army, addUnit, isRegistered, player, balance } = usePlayer() + const [canPurchase, setCanPurchase] = useState>(defaultAvailabilityMap); + const [isShrouded, setIsShrouded] = useState>(defaultAvailabilityMap); + const [canKnowAbout, setCanKnowAbout] = useState>(defaultAvailabilityMap); + + console.log(canPurchase, "\nshroud", isShrouded, "\ncanknowabout", canKnowAbout) + + useEffect(() => { + if (isRegistered) { + const totalMinted = player?.total_minted ?? BigInt(0); + const n_units: Record = { + 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: balance >= calculateUnitPrice(0, n_units[0], 1) && isKnown[0] && !inShroud[0], + 1: balance >= calculateUnitPrice(1, n_units[1], 1) && isKnown[1] && !inShroud[1], + 2: balance >= calculateUnitPrice(2, n_units[2], 1) && isKnown[2] && !inShroud[2], + 3: balance >= calculateUnitPrice(3, n_units[3], 1) && isKnown[3] && !inShroud[3], + }; + setCanPurchase(canActuallyBuy) + setIsShrouded(inShroud) + setCanKnowAbout(isKnown) + } else { + setCanPurchase(defaultAvailabilityMap) + setIsShrouded(defaultAvailabilityMap) + setCanKnowAbout(defaultAvailabilityMap) + } + }, [ + balance, + army, + isRegistered, + player?.total_minted + ]) + + return <> +
+
+ {canKnowAbout[0] && } + {canKnowAbout[1] && } + {canKnowAbout[2] && } + {canKnowAbout[3] && } +
+ +} + export default Army diff --git a/app/src/components/Counter.tsx b/app/src/components/Counter.tsx index c18414c..82850dc 100644 --- a/app/src/components/Counter.tsx +++ b/app/src/components/Counter.tsx @@ -73,7 +73,7 @@ const Counter = () => {

{balanceCount.current} GELD

-

available on chain: {availableBalance.current} GELD

+

in wallet: {availableBalance.current} GELD

} diff --git a/app/src/components/Header.tsx b/app/src/components/Header.tsx index c283eaf..cf88274 100644 --- a/app/src/components/Header.tsx +++ b/app/src/components/Header.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useMemo } from "react" import styles from "../styles/Header.module.css" +import bgStyles from "../styles/Background.module.css" import { usePlayer } from "../providers/PlayerProvider"; import { useAccount } from 'wagmi'; import dynamic from "next/dynamic"; @@ -36,7 +37,7 @@ const Header = () => { }, [isRegistered, register]) return
-

{title}

+

{title}

{subtitle} {perSecondParagraph}
diff --git a/app/src/providers/PlayerProvider.tsx b/app/src/providers/PlayerProvider.tsx index 8221e9b..7f44202 100644 --- a/app/src/providers/PlayerProvider.tsx +++ b/app/src/providers/PlayerProvider.tsx @@ -6,6 +6,8 @@ import contractAddress from '../../contract_address' const abi = contractAbi.abi +export type UnitType = 0 | 1 | 2 | 3 + export interface Player { created_at: bigint, last_raided_at: bigint, @@ -26,7 +28,7 @@ export interface PlayerContextType { balance: bigint, register: () => void, raid: () => void, - addUnit: (unit: number) => void + addUnit: (unit: UnitType) => void } const PlayerContext = createContext({ @@ -91,7 +93,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => { } }); - console.log(player, army) + console.log(balance, player, army) const register = useCallback(() => { writeContract({ @@ -110,7 +112,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => { }) }, [writeContract]) - const addUnit = useCallback((unit: number) => { + const addUnit = useCallback((unit: UnitType) => { writeContract({ abi, address: contractAddress, diff --git a/app/src/styles/Army.module.css b/app/src/styles/Army.module.css index 4412592..8e97598 100644 --- a/app/src/styles/Army.module.css +++ b/app/src/styles/Army.module.css @@ -108,6 +108,72 @@ .hunter { background-image: url("/roles/hunter.svg"); } + +.moloch_denier { + filter: sepia(0.1); +} +.apprentice { +} +.anointed { + filter: saturate(1.1); +} +.champion { + filter: saturate(2); +} + +.armyUnits { + position: absolute; + bottom: 22px; + left: 22px; + right: 22px; + height: 180px; + display: flex; + justify-content: center; + align-items: center; +} +.armyUnit { + position: relative; + height: 100%; + width: 120px; + margin-right: 20px; + .person { + &.isShrouded { + filter: brightness(0); + } + } + &.isUnavailable { + filter: sepia(1); + pointer-events: none; + } +} +.uiElement { + position: absolute; + border-radius: 10px; + background: rgba(0, 0, 0, 0.89); + padding: 0.1rem 1rem; + font-size: 0.8rem; + user-select: none; + text-align: center; +} +.unitSupply { + top: 0; + right: 0; +} +.unitName { + left: 0; + right: 0; + bottom: 45px; +} +.unitPrice { + left: 0; + right: 0; + bottom: 25px; +} +.unitProfit { + left: 0; + right: 0; + bottom: 5px; +} .static { width: 110px; height: 110px; @@ -129,47 +195,18 @@ height: 90px; } .static.moloch_denier { - left: calc(20% - 60px); - bottom: 70px; background-image: url("/roles/scribe2.png"); } .static.apprentice { - left: calc(36% - 60px); background-image: url("/roles/druid2.png"); - bottom: 72px; } .static.anointed { - left: calc(50% - 60px); - bottom: 64px; background-image: url("/roles/ranger2.png"); } .static.champion { - left: calc(64% - 60px); - bottom: 66px; background-image: url("/roles/warrior2.png"); } -.moloch_denier { - filter: sepia(0.1); -} -.apprentice { -} -.anointed { - filter: saturate(1.1); -} -.champion { - filter: saturate(2); -} - -.supply { - position: absolute; - bottom: -40px; - background: rgba(0, 0, 0, 0.89); - padding: 0.5rem 1rem; - font-size: 0.8rem; - user-select: none; -} - @keyframes marching { 0% { transform: translate(-100px, -84px); diff --git a/app/src/styles/Background.module.css b/app/src/styles/Background.module.css index d86f961..90df215 100644 --- a/app/src/styles/Background.module.css +++ b/app/src/styles/Background.module.css @@ -48,7 +48,7 @@ background-image: url("/background/tower.png"); width: 218px; height: 240px; - top: 200px; + top: 150px; animation: thunder_hue_hard 12s linear infinite; transition: all 0.1s cubic-bezier(0.265, 1.4, 0.68, 1.65); transform-origin: bottom center; @@ -82,7 +82,10 @@ } .tower::after { position: absolute; - content: "RAID"; + content: "RAID\A(collect to wallet)"; + text-align: center; + white-space: pre; + word-wrap: break-word; left: 50px; top: 22px; } @@ -225,6 +228,10 @@ } } +.excited { + animation: excited 0.5s infinite linear; +} + @keyframes excited { 0%, 100% { diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index 9b6581e..6d7d276 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -7,22 +7,23 @@ library RaidGeldUtils { uint256 public constant PRECISION = 10000; uint256 constant BASE_PRICE = 380000; uint256 constant PRICE_FACTOR = 11500; + uint256 constant MOLOCH_DENIER_PROFIT = 10000; uint256 constant APPRENTICE_PROFIT = 61000; uint256 constant ANOINTED_PROFIT = 6 * 64000; - uint256 constant CHAMPION_PROFIT = 67000 * 61000 * 64000 / PRECISION / PRECISION; + uint256 constant CHAMPION_PROFIT = 36 * 67000; function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) { require(unit <= 3, "No matching unit found"); uint256 rollingPriceCalculation = uint256(unit + 1) * BASE_PRICE; - uint256 price = rollingPriceCalculation; + uint256 price = 0; // Each level costs 15% more than previous - for (uint256 i = 1; i < currentLevel + units; i++) { - rollingPriceCalculation = rollingPriceCalculation * PRICE_FACTOR / PRECISION; + for (uint256 i = 0; i < currentLevel + units; i++) { if (i >= currentLevel) { price += rollingPriceCalculation; } + rollingPriceCalculation = rollingPriceCalculation * PRICE_FACTOR / PRECISION; } return price; } @@ -30,12 +31,9 @@ library RaidGeldUtils { function calculateProfitsPerSecond(Army memory army) internal pure returns (uint256) { // Each next unit scales progressivelly better - uint256 moloch_denier_profit = army.moloch_denier.level * PRECISION; - + uint256 moloch_denier_profit = army.moloch_denier.level * MOLOCH_DENIER_PROFIT; uint256 apprentice_profit = army.apprentice.level * APPRENTICE_PROFIT; - uint256 anointed_profit = army.anointed.level * ANOINTED_PROFIT; - uint256 champion_profit = army.champion.level * CHAMPION_PROFIT; return moloch_denier_profit + apprentice_profit + anointed_profit + champion_profit;