Compare commits

...

12 Commits

Author SHA1 Message Date
syahirAmali
9a0ab2963a Added events
Some checks failed
CI / Foundry project (push) Has been cancelled
CI / Foundry project (pull_request) Has been cancelled
2024-10-27 02:21:42 +08:00
syahirAmali
dcd91c9316 Merge branch 'main' of https://git.mic0.dev/mico/idle_moloch 2024-10-27 01:04:17 +08:00
syahirAmali
bc11534721 forge install: forge-std
v1.9.4
2024-10-27 00:56:01 +08:00
5491598dfc
Prices in client now reflect those in contract, per second only shows on hover
Some checks are pending
CI / Foundry project (push) Waiting to run
2024-10-26 15:21:27 +02:00
5bb2b5f97b
Enables the counter to display sub zero amounts per second + prices and profits adjusted to match the contract 2024-10-26 15:03:27 +02:00
0ea4ba320c
Balances how much units cost and profit 2024-10-26 14:45:25 +02:00
26b2480b52
Checking for known / shrouded units now calculated with actual total_minted at that moment 2 2024-10-26 13:54:08 +02:00
54b370f41b
Checking for known / shrouded units now calculated with actual total_minted at that moment 2024-10-26 13:53:23 +02:00
84c0aeff03
Better UX: player now can add units wven if funds are not yet minted 2024-10-26 13:49:29 +02:00
fefa2641e4
Fixes broken test
Some checks are pending
CI / Foundry project (push) Waiting to run
2024-10-26 13:16:34 +02:00
203ebd3e97
Removed RAID_WAIT because it was just getting in way of UX
Some checks are pending
CI / Foundry project (push) Waiting to run
2024-10-26 13:14:43 +02:00
c1e6494937
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
2024-10-26 13:12:30 +02:00
11 changed files with 367 additions and 97 deletions

View File

@ -1,26 +1,177 @@
import { usePlayer } from '../providers/PlayerProvider';
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';
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 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}`} />
<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

View File

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

View File

@ -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>

View File

@ -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,

View File

@ -108,6 +108,78 @@
.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;
}
&:hover {
.unitProfit {
display: block;
}
}
}
.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;
display: none;
}
.static {
width: 110px;
height: 110px;
@ -129,47 +201,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);

View File

@ -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% {

@ -1 +1 @@
Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa
Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262

View File

@ -11,11 +11,29 @@ contract RaidGeld is ERC20, Ownable {
uint256 public constant BUY_IN_AMOUNT = 0.00005 ether;
uint256 public constant INITIAL_GELD = 50 * MANTISSA;
uint256 public constant RAID_WAIT = 15 seconds;
mapping(address => Player) private players;
mapping(address => Army) private armies;
// Events
event PlayerRegistered(address indexed player, uint256 initialGeld);
event RaidPerformed(
address indexed player,
uint256 totalMinted,
uint256 geldBalance
);
event UnitAdded(
address indexed player,
uint8 unitType,
uint16 nUnits,
uint256 cost,
uint256 geldBalance,
uint16 molochDenierLevel,
uint16 apprenticeLevel,
uint16 anointedLevel,
uint16 championLevel
);
// Modifier for functions that should only be available to registered players
modifier onlyPlayer() {
require(players[msg.sender].created_at != 0, "Not an initiated player");
@ -26,15 +44,21 @@ contract RaidGeld is ERC20, Ownable {
// This effectively registers the user
function register() external payable {
require(players[msg.sender].created_at == 0, "Whoops, player already exists :)");
require(
players[msg.sender].created_at == 0,
"Whoops, player already exists :)"
);
require(msg.value == BUY_IN_AMOUNT, "Incorrect buy in amount");
// Mint some starting tokens to the player
_mint(msg.sender, INITIAL_GELD);
// Set initial states
players[msg.sender] =
Player({total_minted: INITIAL_GELD, created_at: block.timestamp, last_raided_at: block.timestamp});
players[msg.sender] = Player({
total_minted: INITIAL_GELD,
created_at: block.timestamp,
last_raided_at: block.timestamp
});
armies[msg.sender] = Army({
moloch_denier: Raider({level: 0}),
apprentice: Raider({level: 0}),
@ -42,6 +66,9 @@ contract RaidGeld is ERC20, Ownable {
champion: Raider({level: 0}),
profit_per_second: 0
});
// Emit event
emit PlayerRegistered(msg.sender, INITIAL_GELD);
}
// Override for default number of decimals
@ -56,21 +83,24 @@ contract RaidGeld is ERC20, Ownable {
// Manual minting for itchy fingers
function raid() external onlyPlayer {
require(block.timestamp >= players[msg.sender].last_raided_at + RAID_WAIT, "Tried minting too soon");
performRaid(msg.sender);
uint256 totalMinted = performRaid(msg.sender);
// Emit event
emit RaidPerformed(msg.sender, totalMinted, balanceOf(msg.sender));
}
// Helper so we can use it when buying units too
function performRaid(address player) private {
function performRaid(address player) private returns (uint256) {
uint256 time_past = block.timestamp - players[player].last_raided_at;
uint256 new_geld = armies[player].profit_per_second * time_past;
// TODO: Pink noise, make it so sometimes its better than expected
_mint(player, new_geld);
players[player].last_raided_at = block.timestamp;
players[player].total_minted = new_geld;
players[player].total_minted += new_geld;
return players[player].total_minted;
}
// Function to get Player struct
@ -108,10 +138,24 @@ contract RaidGeld is ERC20, Ownable {
currentLevel = army.champion.level;
}
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
performRaid(msg.sender);
require(balanceOf(msg.sender) > cost, "Not enough GELD to add this much");
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"
);
uint256 totalMinted = performRaid(msg.sender);
// Emit event
emit RaidPerformed(msg.sender, totalMinted, balanceOf(msg.sender));
// 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
@ -136,10 +180,25 @@ contract RaidGeld is ERC20, Ownable {
// update profite per second
army.profit_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
// Emit event
emit UnitAdded(
msg.sender,
unit,
n_units,
cost,
balanceOf(msg.sender),
army.moloch_denier.level,
army.apprentice.level,
army.anointed.level,
army.champion.level
);
}
receive() external payable {
revert("No plain Ether accepted, use register() function to check in :)");
revert(
"No plain Ether accepted, use register() function to check in :)"
);
}
// Revert any non-function-call Ether transfers or calls to non-existent functions

View File

@ -5,24 +5,38 @@ import {Army} from "../src/RaidGeldStructs.sol";
library RaidGeldUtils {
uint256 public constant PRECISION = 10000;
uint256 constant BASE_PRICE = 380000;
uint256 constant PRICE_FACTOR = 11500;
uint256 constant APPRENTICE_PROFIT = 61000;
uint256 constant ANOINTED_PROFIT = 6 * 64000;
uint256 constant CHAMPION_PROFIT = 67000 * 61000 * 64000 / PRECISION / PRECISION;
// base price * (0.00666) * 11 per each next unit
uint256 constant MOLOCH_DENIER_PROFIT = 2533;
uint256 constant APPRENTICE_PROFIT = 27863;
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) {
require(unit <= 3, "No matching unit found");
uint256 rollingPriceCalculation = uint256(unit + 1) * BASE_PRICE;
uint256 price = rollingPriceCalculation;
uint256 rollingPriceCalculation = MOLOCH_DENIER_BASE_COST;
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
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 +44,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;

View File

@ -148,11 +148,10 @@ contract raid_geldTest is Test {
// bought 1 moloch_denier
raid_geld.addUnit(0, 1);
vm.warp(block.timestamp + 15);
uint256 balance = raid_geld.balanceOf(player1);
// Warp time a bit so first raid doesnt fail
vm.warp(block.timestamp + raid_geld.RAID_WAIT());
// Trigger raid funds minting
raid_geld.raid();
@ -162,12 +161,8 @@ contract raid_geldTest is Test {
uint256 last_raided_at = player.last_raided_at;
assertLt(balance, newBalance);
// Expect fail if we raid again, we need to wait a bit
vm.expectRevert();
raid_geld.raid();
// After wait time passes raid should work again
vm.warp(block.timestamp + raid_geld.RAID_WAIT());
// After wait time passes raid should bring in profits again
vm.warp(block.timestamp + 15);
raid_geld.raid();
// Balance should reflect that

View File

@ -9,7 +9,7 @@ contract raid_geldTest is Test {
function test_0_unit_price() public pure {
// buying 1 unit of moloch_denier
uint256 basePriceMolochDenier = RaidGeldUtils.calculateUnitPrice(0, 0, 1);
assertEq(basePriceMolochDenier, RaidGeldUtils.BASE_PRICE);
assertEq(basePriceMolochDenier, RaidGeldUtils.MOLOCH_DENIER_BASE_COST);
// buying 3 units
// 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
});
uint256 profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
assertEq(profits_per_second, RaidGeldUtils.PRECISION);
assertEq(profits_per_second, RaidGeldUtils.MOLOCH_DENIER_PROFIT);
army = Army({
moloch_denier: Raider({level: _dLvl}),
@ -47,7 +47,7 @@ contract raid_geldTest is Test {
profit_per_second: 0 // irrelevant for this test
});
profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
uint256 expected = _dLvl * RaidGeldUtils.PRECISION + _apLvl * RaidGeldUtils.APPRENTICE_PROFIT
uint256 expected = _dLvl * RaidGeldUtils.MOLOCH_DENIER_PROFIT + _apLvl * RaidGeldUtils.APPRENTICE_PROFIT
+ _anLvl * RaidGeldUtils.ANOINTED_PROFIT + _cLvl * RaidGeldUtils.CHAMPION_PROFIT;
assertEq(profits_per_second, expected);
}