Merge pull request 'Mobile version' (#11) from mobileVersion into main
Some checks are pending
CI / Foundry project (push) Waiting to run

Reviewed-on: #11
This commit is contained in:
mic0 2024-10-31 22:54:52 +00:00
commit fc749203b8
23 changed files with 445 additions and 113 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -80,7 +80,7 @@ const unitAvailableToDiscoverAt: Record<UnitType, bigint> = {
}; };
interface UnitProps { interface UnitProps {
addUnit: (unitType: UnitType) => void; addUnit: (unitType: UnitType, amount?: number) => void;
unitType: UnitType; unitType: UnitType;
canPurchase: boolean; canPurchase: boolean;
isShrouded: boolean; isShrouded: boolean;
@ -92,19 +92,20 @@ const Unit = ({
unitType, unitType,
canPurchase, canPurchase,
isShrouded, isShrouded,
n_units, n_units: unitLevel,
}: UnitProps) => { }: UnitProps) => {
const [unitPrice, unitProfit] = useMemo(() => { const [unitPrice, unitProfit] = useMemo(() => {
return [ return [
toReadable(calculateUnitPrice(unitType, n_units, 1)), toReadable(calculateUnitPrice(unitType, unitLevel, 1)),
toReadable(calculateProfitPerSecond(unitType, n_units)), toReadable(calculateProfitPerSecond(unitType, unitLevel)),
]; ];
}, [n_units, unitType]); }, [unitLevel, unitType]);
return ( return (
<div <div
onClick={() => addUnit(unitType)} onClick={() => addUnit(unitType)}
className={`${styles.armyUnit} ${canPurchase ? "" : styles.isUnavailable className={`${styles.armyUnit} ${
}`} canPurchase ? "" : styles.isUnavailable
}`}
> >
<div <div
className={` className={`
@ -122,12 +123,12 @@ const Unit = ({
{unitPrice} <small>GELD</small> {unitPrice} <small>GELD</small>
</span> </span>
)} )}
{n_units > 0 ? ( {unitLevel > 0 ? (
<span className={`${styles.unitSupply} ${styles.uiElement}`}> <span className={`${styles.unitSupply} ${styles.uiElement}`}>
{n_units} {`lvl ${unitLevel}`}
</span> </span>
) : null} ) : null}
{n_units > 0 ? ( {unitLevel > 0 ? (
<span className={`${styles.unitProfit} ${styles.uiElement}`}> <span className={`${styles.unitProfit} ${styles.uiElement}`}>
{unitProfit} <small>per sec</small> {unitProfit} <small>per sec</small>
</span> </span>

View File

@ -2,7 +2,7 @@ import { formatUnits } from "viem"
import { BossLevel, usePlayer } from "../providers/PlayerProvider" import { BossLevel, usePlayer } from "../providers/PlayerProvider"
import styles from "../styles/Info.module.css" import styles from "../styles/Info.module.css"
import { useEffect, useReducer, useRef } from "react" import { useEffect, useReducer, useRef } from "react"
import { calculateBalance } from "./Counter" import { calculateBalance, toReadable } from "./Counter"
export const bossLevelToClass: Record<BossLevel, string> = { export const bossLevelToClass: Record<BossLevel, string> = {
0: styles.boss0, 0: styles.boss0,
@ -14,7 +14,7 @@ export const bossLevelToClass: Record<BossLevel, string> = {
6: styles.boss6, 6: styles.boss6,
} }
const bossToName: Record<BossLevel, string> = { export const bossToName: Record<BossLevel, string> = {
0: "Gluttonous", 0: "Gluttonous",
1: "Slothful", 1: "Slothful",
2: "Lusty", 2: "Lusty",
@ -24,7 +24,7 @@ const bossToName: Record<BossLevel, string> = {
6: "Greedy", 6: "Greedy",
} }
const bossToReward: Record<BossLevel, bigint> = { export const bossToReward: Record<BossLevel, bigint> = {
0: BigInt("200000000000000000"), 0: BigInt("200000000000000000"),
1: BigInt("28274420000000000000"), 1: BigInt("28274420000000000000"),
2: BigInt("174191628800000000000"), 2: BigInt("174191628800000000000"),
@ -70,22 +70,24 @@ const BossInfo = () => {
const [, render] = useReducer(p => !p, false); const [, render] = useReducer(p => !p, false);
useEffect(() => { useEffect(() => {
const tickInterval = setInterval(() => { const tickInterval = setInterval(() => {
chanceToDefeat.current = getBossChanceToDefeat(boss?.level ?? 0, calculateBalance( const _balance = calculateBalance(
balance ?? BigInt(0), balance ?? BigInt(0),
army?.profit_per_second ?? BigInt(0), army?.profit_per_second ?? BigInt(0),
player?.last_raided_at ?? BigInt(0) player?.last_raided_at ?? BigInt(0)
)) );
chanceToDefeat.current = getBossChanceToDefeat(boss?.level ?? 0, _balance)
render(); render();
}, 100); }, 100);
return () => clearInterval(tickInterval) return () => clearInterval(tickInterval)
}, [balance, army?.profit_per_second, player?.last_raided_at, boss?.level]) }, [balance, army?.profit_per_second, player?.last_raided_at, boss?.level])
return <div className={styles.bossInfo}> return <div className={styles.bossInfo}>
<p><strong className={bossLevelToClass[boss?.level || 0]}>{bossToName[variant]}</strong> Moloch <small>(lvl {boss ? boss.level + 1 : 0})</small></p> <p><strong className={bossLevelToClass[variant]}>{bossToName[variant]}</strong> Moloch <small>(lvl {boss ? boss.level + 1 : 0})</small></p>
<p><strong className={styles.reward}>{parseFloat(parseFloat(formatUnits(bossToReward[boss?.level || 0], 18).toString()).toFixed(4))} RGCVII</strong> <small>reward</small></p> <p><strong className={styles.reward}>{parseFloat(parseFloat(formatUnits(bossToReward[boss?.level || 0], 18).toString()).toFixed(4))} RGCVII</strong> <small>reward</small></p>
<p> <p>
<strong>{parseFloat((chanceToDefeat.current * 100).toFixed(2))} % to slay</strong>{" "} <strong>{parseFloat((chanceToDefeat.current * 100).toFixed(2))} % to slay</strong>{" "}
{chanceToDefeat.current == maxChance ? <small className={styles.maxed}>(MAXED)</small> : <small>(Max {maxChance * 100}%)</small>} {chanceToDefeat.current == maxChance ? <small className={styles.maxed}>(MAXED)</small> : <small>(Max {maxChance * 100}%)</small>}
</p> </p>
<p><small>{toReadable(bossToBossPower[boss?.level ?? 0])} GELD = max chance</small></p>
</div> </div>
} }

View File

@ -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 ? <span>and you <strong className={styles.won}>won!</strong> 🤩</span> : <span>and you <strong className={styles.lost}>lost</strong> 😔</span>;
const rewardAmount = parseFloat(parseFloat(formatUnits(bossToReward[lastBossResult.level], 18).toString()).toFixed(4));
const rewardText =
ascended ? <p>You won <strong>{rewardAmount} RGCVII</strong> and <strong>ASCENDED!!!</strong>. This means you beat the bosses and gained a <strong>Prestige level</strong>. Your GELD is now forfeit, but your legend lives on.</p>
: outcome ? <p>You won <strong>{rewardAmount} RGCVII</strong></p>
: <p>Your GELD is now forfeit.<br />Try again 💪 we know you can do it!</p>
const bossName = bossToName[lastBossResult.variant];
const bossClass = bossLevelToClass[lastBossResult.variant];
return <div className={`${styles.modal} ${styles.bossModal}`}>
<h2>You battled {bossName} Moloch!</h2>
<div className={`${bgStyles.boss} ${bossClass} ${styles.image}`} />
<p className={styles.outcome}>{text}</p>
{rewardText}
<div>
<button onClick={() => setIsOpen(false)}>Onward!</button>
</div>
</div>
}
export default BossOutcomeModal

View File

@ -16,25 +16,26 @@ export const calculateBalance = (balance: bigint, perSecond: bigint, lastRaidedA
export const toReadable = (rawValue: bigint) => { export const toReadable = (rawValue: bigint) => {
const value = rawValue / BigInt(10000); const value = rawValue / BigInt(10000);
const suffixes = [ const suffixes = [
{ value: BigInt('1000'), suffix: 'thousand' }, { value: BigInt('1000'), suffix: 'k' }, // Thousand
{ value: BigInt('1000000'), suffix: 'million' }, { value: BigInt('1000000'), suffix: 'M' }, // Million
{ value: BigInt('1000000000'), suffix: 'billion' }, { value: BigInt('1000000000'), suffix: 'B' }, // Billion
{ value: BigInt('1000000000000'), suffix: 'trillion' }, { value: BigInt('1000000000000'), suffix: 'T' }, // Trillion
{ value: BigInt('1000000000000000'), suffix: 'quadrillion' }, { value: BigInt('1000000000000000'), suffix: 'Qd' }, // Quadrillion
{ value: BigInt('1000000000000000000'), suffix: 'quintillion' }, { value: BigInt('1000000000000000000'), suffix: 'Qi' }, // Quintillion
{ value: BigInt('1000000000000000000000'), suffix: 'sextillion' }, { value: BigInt('1000000000000000000000'), suffix: 'Sx' }, // Sextillion
{ value: BigInt('1000000000000000000000000'), suffix: 'septillion' }, { value: BigInt('1000000000000000000000000'), suffix: 'Sp' }, // Septillion
{ value: BigInt('1000000000000000000000000000'), suffix: 'octillion' }, { value: BigInt('1000000000000000000000000000'), suffix: 'Oc' }, // Octillion
{ value: BigInt('1000000000000000000000000000000'), suffix: 'nonillion' }, { value: BigInt('1000000000000000000000000000000'), suffix: 'No' }, // Nonillion
{ value: BigInt('1000000000000000000000000000000000'), suffix: 'decillion' }, { value: BigInt('1000000000000000000000000000000000'), suffix: 'Dc' }, // Decillion
{ value: BigInt('1000000000000000000000000000000000000'), suffix: 'undecillion' }, { value: BigInt('1000000000000000000000000000000000000'), suffix: 'Ud' }, // Undecillion
{ value: BigInt('1000000000000000000000000000000000000000'), suffix: 'duodecillion' }, { value: BigInt('1000000000000000000000000000000000000000'), suffix: 'Dd' }, // Duodecillion
{ value: BigInt('1000000000000000000000000000000000000000000'), suffix: 'tredecillion' }, { value: BigInt('1000000000000000000000000000000000000000000'), suffix: 'Td' }, // Tredecillion
{ value: BigInt('1000000000000000000000000000000000000000000000'), suffix: 'quattuordecillion' }, { value: BigInt('1000000000000000000000000000000000000000000000'), suffix: 'Qt' }, // Quattuordecillion
{ value: BigInt('1000000000000000000000000000000000000000000000000'), suffix: 'quindecillion' }, { value: BigInt('1000000000000000000000000000000000000000000000000'), suffix: 'Qn' }, // Quindecillion
{ value: BigInt('1000000000000000000000000000000000000000000000000000'), suffix: 'sexdecillion' }, { value: BigInt('1000000000000000000000000000000000000000000000000000'), suffix: 'Sd' }, // Sexdecillion
{ value: BigInt('1000000000000000000000000000000000000000000000000000000'), suffix: 'septendecillion' }, { value: BigInt('1000000000000000000000000000000000000000000000000000000'), suffix: 'St' }, // Septendecillion
]; ];
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) {
@ -43,8 +44,8 @@ export const toReadable = (rawValue: bigint) => {
} 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);
const remainder = parseInt(numStr.replace(/0+$/, ''), 10); const remainder = numStr == "0" ? "" : "." + parseInt(numStr.replace(/0+$/, ''), 10);
return `${divided.toString()}.${remainder.toString()} ${suffixes[i - 1].suffix}`; return `${divided.toString()}${remainder.toString()} ${suffixes[i - 1].suffix}`;
} }
} }
} }

View File

@ -1,8 +1,8 @@
import React, { useCallback, useMemo } from "react" import React, { useCallback, useMemo } from "react";
import styles from "../styles/Header.module.css" import styles from "../styles/Header.module.css";
import bgStyles from "../styles/Background.module.css" import bgStyles from "../styles/Background.module.css";
import { usePlayer } from "../providers/PlayerProvider"; import { usePlayer } from "../providers/PlayerProvider";
import { useAccount } from 'wagmi'; import { useAccount } from "wagmi";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import Counter, { toReadable } from "./Counter"; import Counter, { toReadable } from "./Counter";
import { useModal } from "../providers/ModalProvider"; import { useModal } from "../providers/ModalProvider";
@ -13,38 +13,56 @@ const Header = () => {
const { openRegistrationModal } = useModal(); const { openRegistrationModal } = useModal();
const title = useMemo(() => { const title = useMemo(() => {
return isRegistered && !player?.has_active_session ? `You died 😇 Click here to start again and ...` : return isRegistered && !player?.has_active_session
isRegistered ? `SLAY THE MOLOCH` : ? `You died 😇 Click here to start again and ...`
!isConnected ? "Connect your wallet traveler ☝️ and then ..." : : isRegistered
"Click here to start 😈" ? `SLAY THE MOLOCH`
}, [isConnected, isRegistered, player?.has_active_session]) : !isConnected
? "Connect your wallet traveler ☝️ and then ..."
: "Click here to start 😈";
}, [isConnected, isRegistered, player?.has_active_session]);
const subtitle = useMemo(() => { const subtitle = useMemo(() => {
if (isRegistered && player?.has_active_session) { if (isRegistered && player?.has_active_session) {
return <Counter /> return <Counter />;
} else { } else {
return <p className={`${styles.counter} ${isConnected && !player?.has_active_session ? bgStyles.excited : ""}`}>SLAY THE MOLOCH</p> return (
<p
className={`${styles.counter} ${
isConnected && !player?.has_active_session ? bgStyles.excited : ""
}`}
>
SLAY THE MOLOCH
</p>
);
} }
}, [isRegistered, player?.has_active_session, isConnected]) }, [isRegistered, player?.has_active_session, isConnected]);
const perSecondParagraph = useMemo(() => { const perSecondParagraph = useMemo(() => {
const perSecond = toReadable(army?.profit_per_second ?? BigInt(0)) const perSecond = toReadable(army?.profit_per_second ?? BigInt(0));
return (isRegistered && player?.has_active_session) ? return isRegistered && player?.has_active_session ? (
<p className={styles.counter_per_seconds}>per second: {perSecond}</p> <p className={styles.counter_per_seconds}>per second: {perSecond}</p>
: null ) : null;
}, [isRegistered, army?.profit_per_second, player?.has_active_session]) }, [isRegistered, army?.profit_per_second, player?.has_active_session]);
const onRegister = useCallback(() => { const onRegister = useCallback(() => {
if (player?.has_active_session) return if (player?.has_active_session) return;
openRegistrationModal() openRegistrationModal();
}, [player?.has_active_session, openRegistrationModal]) }, [player?.has_active_session, openRegistrationModal]);
return <header onClick={onRegister} className={`${styles.header} ${isConnected && !player?.has_active_session ? styles.clickable : ""}`}> return (
<h1 className={`${styles.title}`}>{title}</h1> <header
{subtitle} onClick={onRegister}
{perSecondParagraph} className={`${styles.header} ${
</header> isConnected && !player?.has_active_session ? styles.clickable : ""
} }`}
>
<h1 className={`${styles.title}`}>{title}</h1>
{subtitle}
{perSecondParagraph}
</header>
);
};
// export default Header // export default Header

View File

@ -151,7 +151,9 @@ const EARLY_GAME_QUOTES = [
function PixelatedQuote() { function PixelatedQuote() {
const { player } = usePlayer(); const { player } = usePlayer();
const [isShown, setIsShown] = useState(true); const [isShown, setIsShown] = useState(true);
const [currentQuote, setCurrentQuote] = useState("Welcome to the Dark Forest!"); const [currentQuote, setCurrentQuote] = useState(
"Welcome to the Dark Forest!"
);
const intervalIdRef = useRef<NodeJS.Timeout | null>(null); const intervalIdRef = useRef<NodeJS.Timeout | null>(null);
const hasShownWelcome = useRef(false); const hasShownWelcome = useRef(false);
@ -185,7 +187,9 @@ function PixelatedQuote() {
} else if (totalMinted < PROGRESSION_TIERS.BEGINNER) { } else if (totalMinted < PROGRESSION_TIERS.BEGINNER) {
// Show early game quotes until player reaches beginner level // Show early game quotes until player reaches beginner level
setCurrentQuote( setCurrentQuote(
EARLY_GAME_QUOTES[Math.floor(Math.random() * EARLY_GAME_QUOTES.length)] EARLY_GAME_QUOTES[
Math.floor(Math.random() * EARLY_GAME_QUOTES.length)
]
); );
} else { } else {
const tier = getQuoteTier(totalMinted); const tier = getQuoteTier(totalMinted);
@ -198,8 +202,8 @@ function PixelatedQuote() {
setTimeout(() => { setTimeout(() => {
setIsShown(false); setIsShown(false);
}, 4000); }, 8000);
}, 6000); }, 10000);
return () => { return () => {
if (intervalIdRef.current !== null) { if (intervalIdRef.current !== null) {

View File

@ -1,6 +1,7 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { usePlayer } from "../providers/PlayerProvider"; import { usePlayer } from "../providers/PlayerProvider";
import styles from "../styles/Modal.module.css"; import styles from "../styles/Modal.module.css";
import bgStyles from "../styles/Background.module.css";
interface RegistrationModalProps { interface RegistrationModalProps {
isOpen: boolean; isOpen: boolean;
@ -14,13 +15,14 @@ const RegistrationModal = ({ isOpen, setIsOpen }: RegistrationModalProps) => {
setIsOpen(false); setIsOpen(false);
}, [register, setIsOpen]) }, [register, setIsOpen])
if (!isOpen) return null; if (!isOpen) return null;
return <div className={styles.modal}> return <div className={bgStyles.leaderboardOverlay}><div className={styles.modal}>
<h2>Insert coins to continue</h2> <span className={styles.closeBtn} onClick={() => setIsOpen(false)}>x</span>
<h2 className={styles.textCenter}>Insert coins to continue</h2>
<div> <div>
<button onClick={() => onRegister("RGCVII")}>500 RGCVII</button> <button onClick={() => onRegister("RGCVII")}>500 RGCVII</button>
<button onClick={() => onRegister("ETH")}>0.0005 ETH</button> <button onClick={() => onRegister("ETH")}>0.0005 ETH</button>
</div> </div>
</div> </div></div>
} }
export default RegistrationModal export default RegistrationModal

View File

@ -1,5 +1,5 @@
import React, { useCallback, useState } from "react" import React, { useCallback, useState } from "react";
import styles from '../styles/Background.module.css'; import styles from "../styles/Background.module.css";
import Tower from "./Tower"; import Tower from "./Tower";
import Army from "./Army"; import Army from "./Army";
import MarchingBand from "./MarchingBand"; import MarchingBand from "./MarchingBand";
@ -17,17 +17,20 @@ const bossToMountainsClass = {
4: styles.mountains4, 4: styles.mountains4,
5: styles.mountains5, 5: styles.mountains5,
6: styles.mountains6, 6: styles.mountains6,
} };
const Scene = () => { const Scene = () => {
const { isRegistered, boss, player } = usePlayer(); const { isRegistered, boss, player } = usePlayer();
const [isLeaderboardOpen, setIsLeaderboardOpen] = useState(false); const [isLeaderboardOpen, setIsLeaderboardOpen] = useState(false);
const handleMusicReady = useCallback((unmute: () => void) => { const handleMusicReady = useCallback(
if (isRegistered) { (unmute: () => void) => {
unmute(); if (isRegistered) {
} unmute();
}, [isRegistered]); }
},
[isRegistered]
);
const variant = boss?.variants[boss.level] || 0; const variant = boss?.variants[boss.level] || 0;
return <div className={styles.frame}> return <div className={styles.frame}>
@ -48,7 +51,7 @@ const Scene = () => {
className={styles.leaderboardButton} className={styles.leaderboardButton}
title="Leaderboard" title="Leaderboard"
> >
📜 🏆 <span className={styles.hideMobile}>Top players</span>
</button> </button>
{isLeaderboardOpen && ( {isLeaderboardOpen && (
<div className={styles.leaderboardOverlay}> <div className={styles.leaderboardOverlay}>
@ -60,10 +63,9 @@ const Scene = () => {
× ×
</button> </button>
<Leaderboard /> <Leaderboard />
</div> </div></div>
</div>
)} )}
</div> </div>
} }
export default Scene export default Scene;

View File

@ -36,6 +36,7 @@ function MyApp({ Component, pageProps }: AppProps) {
h4, h4,
h5, h5,
h6, h6,
button,
.title { .title {
font-family: ${font.style.fontFamily}; font-family: ${font.style.fontFamily};
} }

View File

@ -1,9 +1,9 @@
import { ConnectButton } from '@rainbow-me/rainbowkit'; import { ConnectButton } from "@rainbow-me/rainbowkit";
import type { NextPage } from 'next'; import type { NextPage } from "next";
import Head from 'next/head'; import Head from "next/head";
import styles from '../styles/Home.module.css'; import styles from "../styles/Home.module.css";
import Header from '../components/Header'; import Header from "../components/Header";
import Scene from '../components/Scene'; import Scene from "../components/Scene";
const Home: NextPage = () => { const Home: NextPage = () => {
return ( return (
@ -26,7 +26,10 @@ const Home: NextPage = () => {
</main> </main>
<footer className={styles.footer}> <footer className={styles.footer}>
Made with by your frens at 😈 Slay the Moloch team for Cohort VII of <a href="https://www.raidguild.org/" target="blank">RaidGuild</a> Made with by your frens at 😈 Slay the Moloch team for Cohort VII of{" "}
<a href="https://www.raidguild.org/" target="blank">
RaidGuild
</a>
</footer> </footer>
</div> </div>
); );

View File

@ -1,9 +1,11 @@
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useState } from 'react' import React, { createContext, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react'
import { useAccount, useReadContract, useWriteContract } from 'wagmi' import { useAccount, useReadContract, useWriteContract } from 'wagmi'
import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json" import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json"
import { Hash, parseEther } from 'viem' import { Hash, parseEther } from 'viem'
import contracts from '../../contract_address' import contracts from '../../contract_address'
import WaitingForTxModal from '../components/WaitingForTxModal' import WaitingForTxModal from '../components/WaitingForTxModal'
import BossOutcomeModal from '../components/BossOutcomeModal'
import styles from "../styles/Background.module.css"
const { contractAddress, daoTokenAddress } = contracts const { contractAddress, daoTokenAddress } = contracts
const abi = contractAbi.abi const abi = contractAbi.abi
@ -33,16 +35,25 @@ export interface Boss {
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 { export interface PlayerContextType {
isRegistered: boolean, isRegistered: boolean,
player: null | Player, player: null | Player,
army: null | Army, army: null | Army,
boss: null | Boss, boss: null | Boss,
lastBossResult: null | LastBossResult,
balance: bigint, balance: bigint,
register: (arg: "ETH" | "RGCVII") => void, register: (arg: "ETH" | "RGCVII") => void,
raid: () => void, raid: () => void,
battleWithBoss: () => void; battleWithBoss: () => void;
addUnit: (unit: UnitType) => void addUnit: (unit: UnitType, amount?: number) => void;
} }
const PlayerContext = createContext<PlayerContextType>({ const PlayerContext = createContext<PlayerContextType>({
@ -50,6 +61,7 @@ const PlayerContext = createContext<PlayerContextType>({
player: null, player: null,
army: null, army: null,
boss: null, boss: null,
lastBossResult: null,
balance: BigInt(0), balance: BigInt(0),
register: () => { }, register: () => { },
raid: () => { }, raid: () => { },
@ -61,6 +73,8 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
const { address, isConnected } = useAccount(); const { address, isConnected } = useAccount();
const { writeContract, error } = useWriteContract(); 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(() => { useEffect(() => {
console.warn(error) console.warn(error)
@ -125,6 +139,17 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
} }
}); });
const { data: lastBossResult } = useReadContract({
address: contractAddress,
abi,
functionName: 'getLastBossResult',
args: [address],
query: {
enabled: isConnected,
refetchInterval: 15
}
});
console.log(balance, player, army, boss) console.log(balance, player, army, boss)
const register = useCallback((arg: "RGCVII" | "ETH") => { const register = useCallback((arg: "RGCVII" | "ETH") => {
@ -133,11 +158,12 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
abi, abi,
address: contractAddress, address: contractAddress,
functionName: 'register_eth', functionName: 'register_eth',
value: parseEther("0.0005"), value: parseEther("0.00005"),
}, { }, {
onSuccess: (hash) => { onSuccess: (hash) => {
setHashAndCallback([hash, resetHashAndCallback]) setHashAndCallback([hash, resetHashAndCallback])
} },
onError: () => resetHashAndCallback()
}) })
} else if (arg === "RGCVII") { } else if (arg === "RGCVII") {
writeContract({ writeContract({
@ -156,10 +182,12 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
}, { }, {
onSuccess: (hash) => { onSuccess: (hash) => {
setHashAndCallback([hash, resetHashAndCallback]) setHashAndCallback([hash, resetHashAndCallback])
} },
onError: () => resetHashAndCallback()
}) })
]) ])
} },
onError: () => resetHashAndCallback()
}); });
} }
}, [writeContract, resetHashAndCallback]) }, [writeContract, resetHashAndCallback])
@ -172,7 +200,8 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
}, { }, {
onSuccess: (hash) => { onSuccess: (hash) => {
setHashAndCallback([hash, resetHashAndCallback]) setHashAndCallback([hash, resetHashAndCallback])
} },
onError: () => resetHashAndCallback()
}) })
}, [writeContract, resetHashAndCallback]) }, [writeContract, resetHashAndCallback])
@ -190,8 +219,23 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
abi, abi,
address: contractAddress, address: contractAddress,
functionName: 'battle_with_boss', functionName: 'battle_with_boss',
}, {
onSuccess: (hash) => {
setHashAndCallback([hash, () => resetHashAndCallback()])
},
onError: () => resetHashAndCallback()
}) })
}, [writeContract]) }, [writeContract, resetHashAndCallback])
useEffect(() => {
if (lastBossResult != null) {
if (hasFetchedLastBossFirstTime.current) {
setBossBattlesModalOpen(true);
} else {
hasFetchedLastBossFirstTime.current = true;
}
}
}, [lastBossResult])
return ( return (
<PlayerContext.Provider value={{ <PlayerContext.Provider value={{
@ -200,13 +244,17 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
army: army as Army, army: army as Army,
boss: boss as Boss, boss: boss as Boss,
balance: balance as bigint, balance: balance as bigint,
lastBossResult: lastBossResult as LastBossResult,
register, register,
raid, raid,
addUnit, addUnit,
battleWithBoss battleWithBoss
}}> }}>
{children} {children}
{txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />} <div className={`${(txHash || bossBattledModalOpen) ? styles.leaderboardOverlay : ""}`}>
{txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />}
{bossBattledModalOpen && <BossOutcomeModal setIsOpen={setBossBattlesModalOpen} />}
</div>
</PlayerContext.Provider> </PlayerContext.Provider>
); );
} }

View File

@ -130,8 +130,17 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@media only screen and (max-width: 600px) {
left: 10px;
bottom: 5px;
overflow: auto;
}
} }
.armyUnit { .armyUnit {
@media only screen and (max-width: 600px) {
transform: scale(0.75);
}
position: relative; position: relative;
height: 100%; height: 100%;
width: 120px; width: 120px;
@ -152,6 +161,7 @@
} }
} }
.uiElement { .uiElement {
width: fit-content;
position: absolute; position: absolute;
border-radius: 10px; border-radius: 10px;
background: rgba(0, 0, 0, 0.89); background: rgba(0, 0, 0, 0.89);
@ -161,18 +171,22 @@
text-align: center; text-align: center;
} }
.unitSupply { .unitSupply {
top: 0; top: -30px;
right: 0; right: 0;
left: 2rem;
white-space: nowrap;
} }
.unitName { .unitName {
left: 0; left: 0;
right: 0; right: 0;
bottom: 45px; bottom: 45px;
white-space: nowrap;
} }
.unitPrice { .unitPrice {
left: 0; left: 0;
right: 0; right: 0;
bottom: 25px; bottom: 25px;
white-space: nowrap;
} }
.unitProfit { .unitProfit {
left: 0; left: 0;
@ -201,18 +215,25 @@
height: 90px; height: 90px;
user-select: none; user-select: none;
.pixelQuote { .pixelQuote {
min-width: 150px; z-index: 20;
min-width: 200px;
width: fit-content;
max-width: 300px;
color: black; color: black;
font-size: 0.7rem; font-size: 0.7rem;
line-height: 0.9rem;
position: absolute; position: absolute;
bottom: 5.5rem; bottom: 5.5rem;
left: -20px; left: -70px;
right: 0; right: 0;
padding: 0.7rem; padding: 0.7rem;
line-height: 0.8rem;
transition: opacity 1s ease-in-out; transition: opacity 1s ease-in-out;
box-shadow: 0px 5px 10px 5px rgba(0, 0, 0, 0.4); box-shadow: 0px 5px 10px 5px rgba(0, 0, 0, 0.4);
} }
@media only screen and (max-width: 600px) {
right: 60px;
}
} }
.static.moloch_denier { .static.moloch_denier {
background-image: url("/roles/scribe2.png"); background-image: url("/roles/scribe2.png");
@ -257,6 +278,40 @@
} }
} }
@media only screen and (max-width: 600px) {
@keyframes marching {
0% {
transform: translate(-54px, -59px); /* -100px scaled to ~-54px */
}
8% {
/* approaches fire */
transform: translate(15px, -100px); /* 72px scaled to ~39px */
}
15% {
/* approaches road */
transform: translate(82px, -123px); /* 152px scaled to ~82px */
}
25% {
/* first road turn */
transform: translate(66px, -200px); /* 122px scaled to ~66px */
}
45% {
/* second road turn */
transform: translate(138px, -264px); /* 256px scaled to ~138px */
}
75% {
/* third road turn */
transform: translate(86px, -293px); /* 159px scaled to ~86px */
}
100% {
/* vanishes into distance */
transform: translate(97px, -300px); /* 180px scaled to ~97px */
}
}
}
@keyframes marchingPerson { @keyframes marchingPerson {
0% { 0% {
background-size: 100% 100%; background-size: 100% 100%;

View File

@ -1,11 +1,14 @@
.frame { .frame {
position: absolute; position: absolute;
width: 100%;
height: 100%;
border-width: 8px; border-width: 8px;
border-image: url("/background/frame.png") 22 fill / auto space; border-image: url("/background/frame.png") 22 fill / auto space;
width: 720px; width: 720px;
height: 960px; height: 960px;
@media only screen and (max-width: 600px) {
max-height: 90vh;
overflow: hidden;
max-width: 100vw;
}
} }
.background_asset { .background_asset {
@ -34,16 +37,27 @@
height: 150px; height: 150px;
background-image: url("/background/clouds_large.png"); background-image: url("/background/clouds_large.png");
animation: animation:
scrollBackground 80s linear infinite, scrollBackground 28s linear infinite,
thunder 4s linear infinite; thunder 4s linear infinite;
@media only screen and (max-width: 600px) {
animation:
scrollBackground 280s linear infinite,
thunder 4s linear infinite;
}
} }
.clouds_small { .clouds_small {
top: 270px; top: 275px;
height: 82px; height: 82px;
background-image: url("/background/clouds_small.png"); background-image: url("/background/clouds_small.png");
animation: animation:
scrollBackground 20s linear infinite, scrollBackground 20s linear infinite,
thunder 12s 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 { .boss {
@ -54,6 +68,9 @@
top: 130px; top: 130px;
right: 10px; right: 10px;
left: auto; left: auto;
@media only screen and (max-width: 600px) {
right: -20px;
}
animation: thunder_hue_hard 12s linear infinite; animation: thunder_hue_hard 12s linear infinite;
transition: all 0.1s cubic-bezier(0.265, 1.4, 0.68, 1.65); transition: all 0.1s cubic-bezier(0.265, 1.4, 0.68, 1.65);
transform-origin: bottom center; transform-origin: bottom center;
@ -113,6 +130,9 @@
height: 372px; height: 372px;
top: 90px; top: 90px;
left: -10px; left: -10px;
@media only screen and (max-width: 600px) {
left: -80px;
}
animation: thunder_hue_hard 12s linear infinite; animation: thunder_hue_hard 12s linear infinite;
transition: all 0.1s cubic-bezier(0.265, 1.4, 0.68, 1.65); transition: all 0.1s cubic-bezier(0.265, 1.4, 0.68, 1.65);
transform-origin: bottom center; transform-origin: bottom center;
@ -215,6 +235,11 @@
background-image: url("/background/village.png"); background-image: url("/background/village.png");
height: 540px; height: 540px;
bottom: 22px; bottom: 22px;
@media only screen and (max-width: 600px) {
height: 300px;
bottom: 80px;
}
} }
.bonfire { .bonfire {
background-image: url("/background/bonfire.png"); background-image: url("/background/bonfire.png");
@ -226,6 +251,12 @@
animation: animation:
bonfire 12s linear infinite, bonfire 12s linear infinite,
bonfire_skew 5s infinite linear; bonfire_skew 5s infinite linear;
@media only screen and (max-width: 600px) {
left: 80px;
bottom: 105px;
scale: 0.7;
}
} }
.musicButton { .musicButton {
position: absolute; position: absolute;
@ -373,8 +404,8 @@
.leaderboardButton { .leaderboardButton {
position: absolute; position: absolute;
top: 80px; top: 30px;
right: 30px; left: 80px;
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.5);
border: none; border: none;
border-radius: 5px; border-radius: 5px;
@ -385,6 +416,14 @@
transition: all 0.2s cubic-bezier(0.265, 1.4, 0.68, 1.65); 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); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
color: var(--text-color); color: var(--text-color);
& .hideMobile {
padding-left: 0.5rem;
}
@media only screen and (max-width: 600px) {
& .hideMobile {
display: none;
}
}
} }
.leaderboardButton:hover { .leaderboardButton:hover {

View File

@ -5,10 +5,18 @@
&.clickable { &.clickable {
cursor: pointer; cursor: pointer;
} }
@media only screen and (max-width: 600px) {
margin-top: 3rem;
}
} }
.title { .title {
font-size: 1.5rem; font-size: 1.5rem;
margin: 0; margin: 0;
@media only screen and (max-width: 600px) {
display: none;
}
} }
.counter { .counter {
font-size: 2rem; font-size: 2rem;

View File

@ -18,12 +18,14 @@
max-width: 720px; max-width: 720px;
height: 100vh; height: 100vh;
margin: 0 auto; margin: 0 auto;
@media only screen and (max-width: 600px) {
max-height: 90vh;
}
} }
.footer { .footer {
margin-top: 2rem; padding: 0;
padding: 2rem 0; margin: 10px 0;
border-top: 1px solid #eaeaea;
text-align: center; text-align: center;
} }

View File

@ -1,5 +1,6 @@
.bossInfo { .bossInfo {
position: absolute; position: absolute;
z-index: 10;
top: 350px; top: 350px;
right: 28px; right: 28px;
background: var(--bg-color); background: var(--bg-color);

View File

@ -47,6 +47,65 @@
margin-bottom: 0; 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 { @keyframes spin {
0% { 0% {

View File

@ -2,7 +2,6 @@ import { getDefaultConfig } from '@rainbow-me/rainbowkit';
import { import {
base, base,
baseSepolia, baseSepolia,
sepolia,
foundry foundry
} from 'wagmi/chains'; } from 'wagmi/chains';
@ -13,7 +12,6 @@ export const config = getDefaultConfig({
baseSepolia, baseSepolia,
foundry, foundry,
base, base,
...(process.env.NEXT_PUBLIC_ENABLE_TESTNETS === 'true' ? [sepolia] : []),
], ],
ssr: true, ssr: true,
}); });

View File

@ -5,7 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/access/Ownable.sol";
import {RaidGeldUtils} from "../src/RaidGeldUtils.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"; import "../src/Constants.sol";
contract RaidGeld is ERC20, Ownable, Constants { contract RaidGeld is ERC20, Ownable, Constants {
@ -16,6 +16,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
mapping(address => Player) private players; mapping(address => Player) private players;
mapping(address => Army) private armies; mapping(address => Army) private armies;
mapping(address => Boss) private bosses; mapping(address => Boss) private bosses;
mapping(address => LastBossResult) private lastBossResults;
// WETH // WETH
IWETH public immutable weth = IWETH(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 DaoTokenBuyInAmountSet(address indexed owner, uint256 oldAmount, uint256 newAmount);
event PrestigeGained(address indexed player, uint32 prestigeLevel); event PrestigeGained(address indexed player, uint32 prestigeLevel);
event BossDefeated(address indexed player, uint8 bossLevel, uint256 earnings); 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 for functions that should only be available to registered players
modifier onlyPlayer() { modifier onlyPlayer() {
@ -106,6 +108,9 @@ contract RaidGeld is ERC20, Ownable, Constants {
profit_per_second: 0 profit_per_second: 0
}); });
bosses[_playerAddress] = Boss({level: 0, variants: RaidGeldUtils.generate_boss_variants(block.prevrandao)}); 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 // New player want to register with ETH
@ -171,6 +176,11 @@ contract RaidGeld is ERC20, Ownable, Constants {
return bosses[addr]; 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 // Quick fn to check if user is registered
function isRegistered(address addr) public view returns (bool) { function isRegistered(address addr) public view returns (bool) {
return players[addr].created_at != 0; return players[addr].created_at != 0;
@ -248,6 +258,14 @@ contract RaidGeld is ERC20, Ownable, Constants {
? RaidGeldUtils.getBossPower(boss_to_attack.level) ? RaidGeldUtils.getBossPower(boss_to_attack.level)
: balanceOf(msg.sender); : balanceOf(msg.sender);
bool hasWonBattle = RaidGeldUtils.calculateBossFight(boss_to_attack.level, geld_to_burn, block.prevrandao); 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) { if (hasWonBattle) {
// Burn geld, send some sweet DAO Token and continue // Burn geld, send some sweet DAO Token and continue
_burn(msg.sender, geld_to_burn); _burn(msg.sender, geld_to_burn);
@ -256,11 +274,14 @@ contract RaidGeld is ERC20, Ownable, Constants {
daoToken.transfer(msg.sender, reward); daoToken.transfer(msg.sender, reward);
emit BossDefeated(msg.sender, boss_to_attack.level, reward); emit BossDefeated(msg.sender, boss_to_attack.level, reward);
lastBossResults[msg.sender].reward = reward;
if (boss_to_attack.level == 6) { if (boss_to_attack.level == 6) {
// User ascends! Moloch is defeated, user can start a new run // User ascends! Moloch is defeated, user can start a new run
players[msg.sender].prestige_level += 1; players[msg.sender].prestige_level += 1;
emit PrestigeGained(msg.sender, players[msg.sender].prestige_level); emit PrestigeGained(msg.sender, players[msg.sender].prestige_level);
player_dies(msg.sender); player_dies(msg.sender);
lastBossResults[msg.sender].prestigeGained = true;
return [hasWonBattle, true /* New prestige level! */ ]; return [hasWonBattle, true /* New prestige level! */ ];
} else { } else {
// else go to next boss // else go to next boss
@ -268,6 +289,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
} }
} else { } else {
// Whoops u died, boss defeated you // Whoops u died, boss defeated you
lastBossResults[msg.sender].reward = 0;
player_dies(msg.sender); player_dies(msg.sender);
} }
return [hasWonBattle, false /* hasnt gotten prestige level */ ]; return [hasWonBattle, false /* hasnt gotten prestige level */ ];

View File

@ -28,3 +28,11 @@ struct Boss {
uint8 level; uint8 level;
uint8[7] variants; uint8[7] variants;
} }
struct LastBossResult {
uint256 battled_at;
uint256 reward;
uint8 level;
uint8 variant;
bool prestigeGained;
}

View File

@ -3,7 +3,7 @@ pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol"; import {Test, console} from "forge-std/Test.sol";
import {stdStorage, StdStorage} 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 "../src/RaidGeldUtils.sol";
import {Constants} from "../src/Constants.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 afterBossDaoBalance = raid_geld.daoToken().balanceOf(player1);
uint256 afterBossContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); 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 // User should receive funs, contract should lose them
assertLt(initialDaoBalance, afterBossDaoBalance); assertLt(initialDaoBalance, afterBossDaoBalance);
@ -306,6 +313,7 @@ contract raid_geldTest is Test, Constants {
registerPlayerWithDaoToken(); registerPlayerWithDaoToken();
raid_geld.addUnit(0, 1); raid_geld.addUnit(0, 1);
Boss memory boss = raid_geld.getBoss(player1);
bool[2] memory results = raid_geld.battle_with_boss(); bool[2] memory results = raid_geld.battle_with_boss();
// Should lose with just starting GELD // Should lose with just starting GELD
assertEq(results[0], false); assertEq(results[0], false);
@ -321,6 +329,13 @@ contract raid_geldTest is Test, Constants {
assertEq(raid_geld.balanceOf(player1), 0); assertEq(raid_geld.balanceOf(player1), 0);
// Units should reset // Units should reset
assertEq(army.moloch_denier.level, 0); 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 { 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) { if (results[0] == true && results[1] == true) {
success = true; success = true;
Player memory player = raid_geld.getPlayer(player1); 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.prestige_level, 1);
vm.assertEq(player.n_runs, tries); vm.assertEq(player.n_runs, tries);
break; break;