diff --git a/app/src/components/BossInfo.tsx b/app/src/components/BossInfo.tsx index eede405..a17e8be 100644 --- a/app/src/components/BossInfo.tsx +++ b/app/src/components/BossInfo.tsx @@ -2,7 +2,7 @@ import { formatUnits } from "viem" import { BossLevel, usePlayer } from "../providers/PlayerProvider" import styles from "../styles/Info.module.css" import { useEffect, useReducer, useRef } from "react" -import { calculateBalance } from "./Counter" +import { calculateBalance, toReadable } from "./Counter" export const bossLevelToClass: Record = { 0: styles.boss0, @@ -14,7 +14,7 @@ export const bossLevelToClass: Record = { 6: styles.boss6, } -const bossToName: Record = { +export const bossToName: Record = { 0: "Gluttonous", 1: "Slothful", 2: "Lusty", @@ -24,7 +24,7 @@ const bossToName: Record = { 6: "Greedy", } -const bossToReward: Record = { +export const bossToReward: Record = { 0: BigInt("200000000000000000"), 1: BigInt("28274420000000000000"), 2: BigInt("174191628800000000000"), @@ -70,22 +70,24 @@ const BossInfo = () => { const [, render] = useReducer(p => !p, false); useEffect(() => { const tickInterval = setInterval(() => { - chanceToDefeat.current = getBossChanceToDefeat(boss?.level ?? 0, calculateBalance( + const _balance = calculateBalance( balance ?? BigInt(0), army?.profit_per_second ?? BigInt(0), player?.last_raided_at ?? BigInt(0) - )) + ); + chanceToDefeat.current = getBossChanceToDefeat(boss?.level ?? 0, _balance) render(); }, 100); return () => clearInterval(tickInterval) }, [balance, army?.profit_per_second, player?.last_raided_at, boss?.level]) return
-

{bossToName[variant]} Moloch (lvl {boss ? boss.level + 1 : 0})

+

{bossToName[variant]} Moloch (lvl {boss ? boss.level + 1 : 0})

{parseFloat(parseFloat(formatUnits(bossToReward[boss?.level || 0], 18).toString()).toFixed(4))} RGCVII reward

{parseFloat((chanceToDefeat.current * 100).toFixed(2))} % to slay{" "} {chanceToDefeat.current == maxChance ? (MAXED) : (Max {maxChance * 100}%)}

+

{toReadable(bossToBossPower[boss?.level ?? 0])} GELD = max chance

} diff --git a/app/src/components/BossOutcomeModal.tsx b/app/src/components/BossOutcomeModal.tsx new file mode 100644 index 0000000..793b082 --- /dev/null +++ b/app/src/components/BossOutcomeModal.tsx @@ -0,0 +1,41 @@ +import { formatUnits } from "viem"; +import { usePlayer } from "../providers/PlayerProvider"; +import styles from "../styles/Modal.module.css"; +import bgStyles from "../styles/Background.module.css"; +import { bossToName, bossToReward } from "./BossInfo"; +import { bossLevelToClass } from "./Boss"; + + +interface BossOutcomeModalProps { + setIsOpen: (val: boolean) => void, +} + +const BossOutcomeModal = ({ setIsOpen }: BossOutcomeModalProps) => { + const { lastBossResult } = usePlayer(); + if (lastBossResult == null) return null; + + const outcome = lastBossResult.reward != BigInt(0); + const ascended = lastBossResult.prestigeGained; + + const text = outcome ? and you won! 🤩 : and you lost 😔; + const rewardAmount = parseFloat(parseFloat(formatUnits(bossToReward[lastBossResult.level], 18).toString()).toFixed(4)); + const rewardText = + ascended ?

You won {rewardAmount} RGCVII and ASCENDED!!!. This means you beat the bosses and gained a Prestige level. Your GELD is now forfeit, but your legend lives on.

+ : outcome ?

You won {rewardAmount} RGCVII

+ :

Your GELD is now forfeit.
Try again 💪 we know you can do it!

+ + const bossName = bossToName[lastBossResult.variant]; + const bossClass = bossLevelToClass[lastBossResult.variant]; + + return
+

You battled {bossName} Moloch!

+
+

{text}

+ {rewardText} +
+ +
+
+} + +export default BossOutcomeModal diff --git a/app/src/components/Counter.tsx b/app/src/components/Counter.tsx index 02b852f..41e2646 100644 --- a/app/src/components/Counter.tsx +++ b/app/src/components/Counter.tsx @@ -44,8 +44,8 @@ export const toReadable = (rawValue: bigint) => { } else { const divided = value / suffixes[i - 1].value; const numStr = (value % suffixes[i - 1].value).toString().slice(0, 3); - const remainder = parseInt(numStr.replace(/0+$/, ''), 10); - return `${divided.toString()}.${remainder.toString()} ${suffixes[i - 1].suffix}`; + const remainder = numStr == "0" ? "" : "." + parseInt(numStr.replace(/0+$/, ''), 10); + return `${divided.toString()}${remainder.toString()} ${suffixes[i - 1].suffix}`; } } } diff --git a/app/src/components/PixelatedQuote.tsx b/app/src/components/PixelatedQuote.tsx index b87e8ad..311477e 100644 --- a/app/src/components/PixelatedQuote.tsx +++ b/app/src/components/PixelatedQuote.tsx @@ -188,7 +188,7 @@ function PixelatedQuote() { // Show early game quotes until player reaches beginner level setCurrentQuote( EARLY_GAME_QUOTES[ - Math.floor(Math.random() * EARLY_GAME_QUOTES.length) + Math.floor(Math.random() * EARLY_GAME_QUOTES.length) ] ); } else { @@ -202,7 +202,7 @@ function PixelatedQuote() { setTimeout(() => { setIsShown(false); - }, 5000); + }, 8000); }, 10000); return () => { diff --git a/app/src/components/RegistrationModal.tsx b/app/src/components/RegistrationModal.tsx index 540d8ab..ed05ec6 100644 --- a/app/src/components/RegistrationModal.tsx +++ b/app/src/components/RegistrationModal.tsx @@ -1,6 +1,7 @@ import { useCallback } from "react"; import { usePlayer } from "../providers/PlayerProvider"; import styles from "../styles/Modal.module.css"; +import bgStyles from "../styles/Background.module.css"; interface RegistrationModalProps { isOpen: boolean; @@ -14,13 +15,14 @@ const RegistrationModal = ({ isOpen, setIsOpen }: RegistrationModalProps) => { setIsOpen(false); }, [register, setIsOpen]) if (!isOpen) return null; - return
-

Insert coins to continue

+ return
+ setIsOpen(false)}>x +

Insert coins to continue

-
+
} export default RegistrationModal diff --git a/app/src/components/Scene.tsx b/app/src/components/Scene.tsx index db98050..e891639 100644 --- a/app/src/components/Scene.tsx +++ b/app/src/components/Scene.tsx @@ -33,44 +33,39 @@ const Scene = () => { ); const variant = boss?.variants[boss.level] || 0; - return ( -
-
-
- - -
-
-
- {isRegistered && player?.has_active_session && } - -
- - - - {isLeaderboardOpen && ( -
-
- - -
-
- )} -
- ); -}; + return
+
+
+ + +
+
+
+ {isRegistered && player?.has_active_session && } + +
+ + + + {isLeaderboardOpen && ( +
+
+ + +
+ )} +
+} export default Scene; diff --git a/app/src/pages/_app.tsx b/app/src/pages/_app.tsx index 533f4e8..23c9c58 100644 --- a/app/src/pages/_app.tsx +++ b/app/src/pages/_app.tsx @@ -36,6 +36,7 @@ function MyApp({ Component, pageProps }: AppProps) { h4, h5, h6, + button, .title { font-family: ${font.style.fontFamily}; } diff --git a/app/src/providers/PlayerProvider.tsx b/app/src/providers/PlayerProvider.tsx index 24cb2df..199fe9f 100644 --- a/app/src/providers/PlayerProvider.tsx +++ b/app/src/providers/PlayerProvider.tsx @@ -1,61 +1,57 @@ -import React, { - createContext, - ReactNode, - useCallback, - useContext, - useEffect, - useState, -} from "react"; -import { useAccount, useReadContract, useWriteContract } from "wagmi"; -import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json"; -import { Hash, parseEther } from "viem"; -import contracts from "../../contract_address"; -import WaitingForTxModal from "../components/WaitingForTxModal"; +import React, { createContext, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react' +import { useAccount, useReadContract, useWriteContract } from 'wagmi' +import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json" +import { Hash, parseEther } from 'viem' +import contracts from '../../contract_address' +import WaitingForTxModal from '../components/WaitingForTxModal' +import BossOutcomeModal from '../components/BossOutcomeModal' +import styles from "../styles/Background.module.css" -const { contractAddress, daoTokenAddress } = contracts; -const abi = contractAbi.abi; +const { contractAddress, daoTokenAddress } = contracts +const abi = contractAbi.abi -export type UnitType = 0 | 1 | 2 | 3; -export type BossLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6; +export type UnitType = 0 | 1 | 2 | 3 +export type BossLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 export interface Player { - created_at: bigint; - last_raided_at: bigint; - total_minted: bigint; - total_rewards: bigint; - n_runs: number; - prestige_level: number; - is_registered: boolean; - has_active_session: boolean; + created_at: bigint, + last_raided_at: bigint, + total_minted: bigint + total_rewards: bigint, + n_runs: number, + prestige_level: number, + is_registered: boolean, + has_active_session: boolean, } export interface Army { - anointed: { level: number }; - apprentice: { level: number }; - champion: { level: number }; - moloch_denier: { level: number }; - profit_per_second: bigint; + anointed: { level: number } + apprentice: { level: number } + champion: { level: number } + moloch_denier: { level: number } + profit_per_second: bigint } export interface Boss { level: BossLevel; - variants: [ - BossLevel, - BossLevel, - BossLevel, - BossLevel, - BossLevel, - BossLevel, - BossLevel - ]; + variants: [BossLevel, BossLevel, BossLevel, BossLevel, BossLevel, BossLevel, BossLevel] +} + +export interface LastBossResult { + level: BossLevel; + variant: BossLevel; + battled_at: bigint; + reward: bigint; + prestigeGained: boolean; } export interface PlayerContextType { - isRegistered: boolean; - player: null | Player; - army: null | Army; - boss: null | Boss; - balance: bigint; - register: (arg: "ETH" | "RGCVII") => void; - raid: () => void; + isRegistered: boolean, + player: null | Player, + army: null | Army, + boss: null | Boss, + lastBossResult: null | LastBossResult, + balance: bigint, + register: (arg: "ETH" | "RGCVII") => void, + raid: () => void, battleWithBoss: () => void; addUnit: (unit: UnitType, amount?: number) => void; } @@ -65,192 +61,207 @@ const PlayerContext = createContext({ player: null, army: null, boss: null, + lastBossResult: null, balance: BigInt(0), - register: () => {}, - raid: () => {}, - battleWithBoss: () => {}, - addUnit: () => {}, + register: () => { }, + raid: () => { }, + battleWithBoss: () => { }, + addUnit: () => { } }); const PlayerProvider = ({ children }: { children: ReactNode }) => { const { address, isConnected } = useAccount(); const { writeContract, error } = useWriteContract(); - const [[txHash, callbackFn], setHashAndCallback] = useState< - [Hash | null, () => void] - >([null, () => {}]); + const [[txHash, callbackFn], setHashAndCallback] = useState<[Hash | null, () => void]>([null, () => { }]) + const [bossBattledModalOpen, setBossBattlesModalOpen] = useState(false); + const hasFetchedLastBossFirstTime = useRef(false); useEffect(() => { - console.error(error?.message); - }, [error]); + console.warn(error) + }, [error]) const resetHashAndCallback = useCallback(() => { - setHashAndCallback([null, () => {}]); - }, []); + setHashAndCallback([null, () => { }]) + }, []) const { data: isRegistered } = useReadContract({ address: contractAddress, abi, - functionName: "isRegistered", + functionName: 'isRegistered', args: [address], query: { enabled: isConnected, refetchInterval: 15, - }, + } }); - const { data: balance } = useReadContract({ + const { data: balance, } = useReadContract({ address: contractAddress, abi, - functionName: "balanceOf", + functionName: 'balanceOf', args: [address], query: { refetchInterval: 15, - enabled: isConnected, - }, + enabled: isConnected + } }); const { data: player } = useReadContract({ address: contractAddress, abi, - functionName: "getPlayer", + functionName: 'getPlayer', args: [address], query: { enabled: isConnected, - refetchInterval: 15, - }, + refetchInterval: 15 + } }); const { data: army } = useReadContract({ address: contractAddress, abi, - functionName: "getArmy", + functionName: 'getArmy', args: [address], query: { enabled: isConnected, - refetchInterval: 15, - }, + refetchInterval: 15 + } }); const { data: boss } = useReadContract({ address: contractAddress, abi, - functionName: "getBoss", + functionName: 'getBoss', args: [address], query: { enabled: isConnected, - refetchInterval: 15, - }, + refetchInterval: 15 + } }); - console.log(balance, player, army, boss); + const { data: lastBossResult } = useReadContract({ + address: contractAddress, + abi, + functionName: 'getLastBossResult', + args: [address], + query: { + enabled: isConnected, + refetchInterval: 15 + } + }); - const register = useCallback( - (arg: "RGCVII" | "ETH") => { - if (arg === "ETH") { - writeContract( - { - abi, - address: contractAddress, - functionName: "register_eth", - value: parseEther("0.00005"), - }, - { - onSuccess: (hash) => { - setHashAndCallback([hash, resetHashAndCallback]); - }, - } - ); - } else if (arg === "RGCVII") { - writeContract( - { - abi, - address: daoTokenAddress, - functionName: "approve", - args: [contractAddress, parseEther("500")], - }, - { - onSuccess: (hash) => { - setHashAndCallback([ - hash, - () => - writeContract( - { - abi, - address: contractAddress, - functionName: "register_dao", - }, - { - onSuccess: (hash) => { - setHashAndCallback([hash, resetHashAndCallback]); - }, - } - ), - ]); - }, - } - ); - } - }, - [writeContract, resetHashAndCallback] - ); + console.log(balance, player, army, boss) - const raid = useCallback(() => { - writeContract( - { - abi, - address: contractAddress, - functionName: "raid", - }, - { - onSuccess: (hash) => { - setHashAndCallback([hash, resetHashAndCallback]); - }, - } - ); - }, [writeContract, resetHashAndCallback]); - - const addUnit = useCallback( - (unit: UnitType, amount: number = 1) => { + const register = useCallback((arg: "RGCVII" | "ETH") => { + if (arg === 'ETH') { writeContract({ abi, address: contractAddress, - functionName: "addUnit", - args: [unit, amount], + functionName: 'register_eth', + value: parseEther("0.00005"), + }, { + onSuccess: (hash) => { + setHashAndCallback([hash, resetHashAndCallback]) + }, + onError: () => resetHashAndCallback() + }) + } else if (arg === "RGCVII") { + writeContract({ + abi, + address: daoTokenAddress, + functionName: 'approve', + args: [contractAddress, parseEther("500")], + }, { + onSuccess: (hash) => { + setHashAndCallback([ + hash, + () => writeContract({ + abi, + address: contractAddress, + functionName: 'register_dao', + }, { + onSuccess: (hash) => { + setHashAndCallback([hash, resetHashAndCallback]) + }, + onError: () => resetHashAndCallback() + }) + ]) + }, + onError: () => resetHashAndCallback() }); - }, - [writeContract] - ); + } + }, [writeContract, resetHashAndCallback]) + + const raid = useCallback(() => { + writeContract({ + abi, + address: contractAddress, + functionName: 'raid', + }, { + onSuccess: (hash) => { + setHashAndCallback([hash, resetHashAndCallback]) + }, + onError: () => resetHashAndCallback() + }) + }, [writeContract, resetHashAndCallback]) + + const addUnit = useCallback((unit: UnitType) => { + writeContract({ + abi, + address: contractAddress, + functionName: 'addUnit', + args: [unit, 1] + }) + }, [writeContract]) const battleWithBoss = useCallback(() => { writeContract({ abi, address: contractAddress, - functionName: "battle_with_boss", - }); - }, [writeContract]); + functionName: 'battle_with_boss', + }, { + onSuccess: (hash) => { + setHashAndCallback([hash, () => resetHashAndCallback()]) + }, + onError: () => resetHashAndCallback() + }) + }, [writeContract, resetHashAndCallback]) + + useEffect(() => { + if (lastBossResult != null) { + if (hasFetchedLastBossFirstTime.current) { + setBossBattlesModalOpen(true); + } else { + hasFetchedLastBossFirstTime.current = true; + } + } + }, [lastBossResult]) return ( - + {children} - {txHash && } +
+ {txHash && } + {bossBattledModalOpen && } +
); -}; +} export const usePlayer = () => { return useContext(PlayerContext); -}; +} + +export default PlayerProvider -export default PlayerProvider; diff --git a/app/src/styles/Background.module.css b/app/src/styles/Background.module.css index 255751d..2d426d6 100644 --- a/app/src/styles/Background.module.css +++ b/app/src/styles/Background.module.css @@ -1,17 +1,13 @@ .frame { position: absolute; - width: 100%; - height: 100%; border-width: 8px; border-image: url("/background/frame.png") 22 fill / auto space; width: 720px; height: 960px; - - max-height: 90vh; @media only screen and (max-width: 600px) { + max-height: 90vh; overflow: hidden; max-width: 100vw; - max-height: 90vh; } } @@ -41,16 +37,27 @@ height: 150px; background-image: url("/background/clouds_large.png"); animation: - scrollBackground 280s linear infinite, + scrollBackground 28s linear infinite, thunder 4s linear infinite; + @media only screen and (max-width: 600px) { + animation: + scrollBackground 280s linear infinite, + thunder 4s linear infinite; + } } .clouds_small { - top: 285px; + top: 275px; height: 82px; background-image: url("/background/clouds_small.png"); animation: - scrollBackground 200s linear infinite, + scrollBackground 20s linear infinite, thunder 12s linear infinite; + @media only screen and (max-width: 600px) { + top: 285px; + animation: + scrollBackground 200s linear infinite, + thunder 12s linear infinite; + } } .boss { @@ -248,7 +255,7 @@ @media only screen and (max-width: 600px) { left: 80px; bottom: 105px; - scale: .7; + scale: 0.7; } } .musicButton { @@ -397,8 +404,8 @@ .leaderboardButton { position: absolute; - top: 80px; - right: 30px; + top: 30px; + left: 80px; background: rgba(0, 0, 0, 0.5); border: none; border-radius: 5px; @@ -409,6 +416,14 @@ transition: all 0.2s cubic-bezier(0.265, 1.4, 0.68, 1.65); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); color: var(--text-color); + & .hideMobile { + padding-left: 0.5rem; + } + @media only screen and (max-width: 600px) { + & .hideMobile { + display: none; + } + } } .leaderboardButton:hover { diff --git a/app/src/styles/Home.module.css b/app/src/styles/Home.module.css index 0db0a81..4b20934 100644 --- a/app/src/styles/Home.module.css +++ b/app/src/styles/Home.module.css @@ -16,14 +16,16 @@ align-items: center; width: 100%; max-width: 720px; + height: 100vh; margin: 0 auto; - - height: 90vh; + @media only screen and (max-width: 600px) { + max-height: 90vh; + } } .footer { padding: 0; - margin-top: 10px; + margin: 10px 0; text-align: center; } diff --git a/app/src/styles/Modal.module.css b/app/src/styles/Modal.module.css index a1ec842..08a220d 100644 --- a/app/src/styles/Modal.module.css +++ b/app/src/styles/Modal.module.css @@ -47,6 +47,65 @@ margin-bottom: 0; } } +.bossModal { + padding: 32px; + z-index: 3; + max-width: 100%; + width: 500px; + text-align: center; + margin-top: 50px; + .outcome { + font-size: 1.7rem; + } + .image { + position: relative; + margin: 0 auto; + top: 0; + &::after { + display: none; + } + } + & p { + margin: 0.5rem 0; + } + & button { + margin: 1rem; + } + .lost { + color: var(--accent-color); + } + .won { + color: var(--hover-color); + } + .lost, + .won { + font-size: 2rem; + } +} +.textCenter { + text-align: center; +} +.closeBtn { + display: inline-block; + width: 32px; + height: 32px; + text-align: center; + line-height: 26px; + border-radius: 4px; + position: absolute; + right: 32px; + top: 32px; + background: black; + cursor: pointer; + transition: all 0.2s ease; +} +.closeBtn:hover { + color: var(--hover-color); + transform: scale(1.1); +} +.closeBtn::active { + transform: scale(1.2); +} @keyframes spin { 0% { diff --git a/app/src/wagmi.ts b/app/src/wagmi.ts index 332811c..9cf52cd 100644 --- a/app/src/wagmi.ts +++ b/app/src/wagmi.ts @@ -2,7 +2,6 @@ import { getDefaultConfig } from '@rainbow-me/rainbowkit'; import { base, baseSepolia, - sepolia, foundry } from 'wagmi/chains'; @@ -13,7 +12,6 @@ export const config = getDefaultConfig({ baseSepolia, foundry, base, - ...(process.env.NEXT_PUBLIC_ENABLE_TESTNETS === 'true' ? [sepolia] : []), ], ssr: true, }); diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 448efee..3291252 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 448efeea6640bbbc09373f03fbc9c88e280147ba +Subproject commit 3291252c866ad698f6a55ec660259e49a67eb3d0 diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 6f69b2e..ed70fa9 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -5,7 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import {RaidGeldUtils} from "../src/RaidGeldUtils.sol"; -import {Army, Player, Raider, Boss} from "../src/RaidGeldStructs.sol"; +import {Army, Player, Raider, Boss, LastBossResult} from "../src/RaidGeldStructs.sol"; import "../src/Constants.sol"; contract RaidGeld is ERC20, Ownable, Constants { @@ -16,6 +16,7 @@ contract RaidGeld is ERC20, Ownable, Constants { mapping(address => Player) private players; mapping(address => Army) private armies; mapping(address => Boss) private bosses; + mapping(address => LastBossResult) private lastBossResults; // WETH IWETH public immutable weth = IWETH(WETH); @@ -44,6 +45,7 @@ contract RaidGeld is ERC20, Ownable, Constants { event DaoTokenBuyInAmountSet(address indexed owner, uint256 oldAmount, uint256 newAmount); event PrestigeGained(address indexed player, uint32 prestigeLevel); event BossDefeated(address indexed player, uint8 bossLevel, uint256 earnings); + event BossBattle(address indexed player, uint8 bossLevel, bool hasWon); // Modifier for functions that should only be available to registered players modifier onlyPlayer() { @@ -106,6 +108,9 @@ contract RaidGeld is ERC20, Ownable, Constants { profit_per_second: 0 }); bosses[_playerAddress] = Boss({level: 0, variants: RaidGeldUtils.generate_boss_variants(block.prevrandao)}); + + // dont change lastBossResult + // that only changes after boss battles and on init } // New player want to register with ETH @@ -171,6 +176,11 @@ contract RaidGeld is ERC20, Ownable, Constants { return bosses[addr]; } + // Function to get the Boss struct + function getLastBossResult(address addr) public view returns (LastBossResult memory) { + return lastBossResults[addr]; + } + // Quick fn to check if user is registered function isRegistered(address addr) public view returns (bool) { return players[addr].created_at != 0; @@ -248,6 +258,14 @@ contract RaidGeld is ERC20, Ownable, Constants { ? RaidGeldUtils.getBossPower(boss_to_attack.level) : balanceOf(msg.sender); bool hasWonBattle = RaidGeldUtils.calculateBossFight(boss_to_attack.level, geld_to_burn, block.prevrandao); + emit BossBattle(msg.sender, boss_to_attack.level, hasWonBattle); + lastBossResults[msg.sender] = LastBossResult({ + battled_at: block.timestamp, + level: boss_to_attack.level, + variant: boss_to_attack.variants[boss_to_attack.level], + prestigeGained: false, + reward: 0 + }); if (hasWonBattle) { // Burn geld, send some sweet DAO Token and continue _burn(msg.sender, geld_to_burn); @@ -256,11 +274,14 @@ contract RaidGeld is ERC20, Ownable, Constants { daoToken.transfer(msg.sender, reward); emit BossDefeated(msg.sender, boss_to_attack.level, reward); + lastBossResults[msg.sender].reward = reward; + if (boss_to_attack.level == 6) { // User ascends! Moloch is defeated, user can start a new run players[msg.sender].prestige_level += 1; emit PrestigeGained(msg.sender, players[msg.sender].prestige_level); player_dies(msg.sender); + lastBossResults[msg.sender].prestigeGained = true; return [hasWonBattle, true /* New prestige level! */ ]; } else { // else go to next boss @@ -268,6 +289,7 @@ contract RaidGeld is ERC20, Ownable, Constants { } } else { // Whoops u died, boss defeated you + lastBossResults[msg.sender].reward = 0; player_dies(msg.sender); } return [hasWonBattle, false /* hasnt gotten prestige level */ ]; diff --git a/src/RaidGeldStructs.sol b/src/RaidGeldStructs.sol index f30deed..e045916 100644 --- a/src/RaidGeldStructs.sol +++ b/src/RaidGeldStructs.sol @@ -28,3 +28,11 @@ struct Boss { uint8 level; uint8[7] variants; } + +struct LastBossResult { + uint256 battled_at; + uint256 reward; + uint8 level; + uint8 variant; + bool prestigeGained; +} diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index 1d32a09..54dda56 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; import {stdStorage, StdStorage} from "forge-std/Test.sol"; -import {RaidGeld, Army, Player, Boss} from "../src/RaidGeld.sol"; +import {RaidGeld, Army, Player, Boss, LastBossResult} from "../src/RaidGeld.sol"; import "../src/RaidGeldUtils.sol"; import {Constants} from "../src/Constants.sol"; @@ -283,6 +283,13 @@ contract raid_geldTest is Test, Constants { uint256 afterBossDaoBalance = raid_geld.daoToken().balanceOf(player1); uint256 afterBossContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); + LastBossResult memory bossResult = raid_geld.getLastBossResult(player1); + + assertEq(bossResult.battled_at, block.timestamp); + assertEq(bossResult.reward, afterBossDaoBalance - initialDaoBalance); + assertEq(bossResult.level, boss.level); + assertEq(bossResult.variant, boss.variants[boss.level]); + assertEq(bossResult.prestigeGained, false); // User should receive funs, contract should lose them assertLt(initialDaoBalance, afterBossDaoBalance); @@ -306,6 +313,7 @@ contract raid_geldTest is Test, Constants { registerPlayerWithDaoToken(); raid_geld.addUnit(0, 1); + Boss memory boss = raid_geld.getBoss(player1); bool[2] memory results = raid_geld.battle_with_boss(); // Should lose with just starting GELD assertEq(results[0], false); @@ -321,6 +329,13 @@ contract raid_geldTest is Test, Constants { assertEq(raid_geld.balanceOf(player1), 0); // Units should reset assertEq(army.moloch_denier.level, 0); + + LastBossResult memory bossResult = raid_geld.getLastBossResult(player1); + assertEq(bossResult.battled_at, block.timestamp); + assertEq(bossResult.reward, 0); + assertEq(bossResult.level, boss.level); + assertEq(bossResult.variant, boss.variants[boss.level]); + assertEq(bossResult.prestigeGained, false); } function test_08_player_who_lost_can_restart() public { @@ -388,6 +403,8 @@ contract raid_geldTest is Test, Constants { if (results[0] == true && results[1] == true) { success = true; Player memory player = raid_geld.getPlayer(player1); + LastBossResult memory bossResult = raid_geld.getLastBossResult(player1); + assertEq(bossResult.prestigeGained, true); vm.assertEq(player.prestige_level, 1); vm.assertEq(player.n_runs, tries); break;