Added shrouding of units & display of per second profits on units plus fixed calc bug on unit costs
Some checks are pending
CI / Foundry project (push) Waiting to run
Some checks are pending
CI / Foundry project (push) Waiting to run
This commit is contained in:
parent
eb318972b8
commit
c1e6494937
@ -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 <div className="styles.armyGathering">
|
||||
<div className={`${styles.tavern_keeper} ${styles.person} ${styles.static}`} />
|
||||
{isRegistered && <>
|
||||
<div onClick={() => addUnit(0)} className={`${styles.scribe} ${styles.person} ${styles.moloch_denier} ${styles.static}`}>
|
||||
<div className={styles.supply}>Moloch denier: {army?.moloch_denier.level}</div>
|
||||
</div>
|
||||
<div onClick={() => addUnit(1)} className={`${styles.druid} ${styles.person} ${styles.apprentice} ${styles.static}`} >
|
||||
<div className={styles.supply}>Apprentice: {army?.apprentice.level}</div>
|
||||
</div>
|
||||
<div onClick={() => addUnit(2)} className={`${styles.ranger} ${styles.person} ${styles.anointed} ${styles.static}`} >
|
||||
<div className={styles.supply}>Anointed: {army?.anointed.level}</div>
|
||||
</div>
|
||||
<div onClick={() => addUnit(3)} className={`${styles.warrior} ${styles.person} ${styles.champion} ${styles.static}`} >
|
||||
<div className={styles.supply}>Champion: {army?.champion.level}</div>
|
||||
</div>
|
||||
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<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(2_000_0000),
|
||||
2: BigInt(2_000_000_0000),
|
||||
3: BigInt(200_000_000_0000)
|
||||
}
|
||||
const unitAvailableToDiscoverAt: Record<UnitType, bigint> = {
|
||||
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 <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}</span>}
|
||||
{n_units > 0 ? <span className={`${styles.unitSupply} ${styles.uiElement}`}>{n_units}</span> : null}
|
||||
{n_units > 0 ? <span className={`${styles.unitProfit} ${styles.uiElement}`}>{unitProfit}</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);
|
||||
|
||||
console.log(canPurchase, "\nshroud", isShrouded, "\ncanknowabout", canKnowAbout)
|
||||
|
||||
useEffect(() => {
|
||||
if (isRegistered) {
|
||||
const totalMinted = player?.total_minted ?? 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: 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 <>
|
||||
<div className={`${styles.tavern_keeper} ${styles.person} ${styles.static}`} />
|
||||
<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
|
||||
|
||||
@ -73,7 +73,7 @@ const Counter = () => {
|
||||
<p className={styles.counter}>
|
||||
{balanceCount.current} GELD
|
||||
</p>
|
||||
<p className={styles.counter_available}>available on chain: {availableBalance.current} GELD</p>
|
||||
<p className={styles.counter_available}>in wallet: {availableBalance.current} GELD</p>
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -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 <header onClick={onRegister} className={styles.header}>
|
||||
<h1 className={styles.title}>{title}</h1>
|
||||
<h1 className={`${styles.title} ${isConnected && !isRegistered ? bgStyles.excited : ""}`}>{title}</h1>
|
||||
{subtitle}
|
||||
{perSecondParagraph}
|
||||
</header>
|
||||
|
||||
@ -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<PlayerContextType>({
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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% {
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user