forked from mico/idle_moloch
Compare commits
14 Commits
leaderboar
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| cc9cb65e4e | |||
| 88a4400307 | |||
| e7aa29fab7 | |||
| edbee4eb0c | |||
| b5dbe91e8a | |||
| 8e10b9e585 | |||
| 711e092e5b | |||
| 404a23b2e1 | |||
| bd0cc6c34d | |||
| 91185efa18 | |||
| a5fd3d9e21 | |||
| e0b99b9d39 | |||
| 32aaf80c17 | |||
| eb4ce2d2c7 |
15
app/package-lock.json
generated
15
app/package-lock.json
generated
@ -11,6 +11,8 @@
|
||||
"@next/eslint-plugin-next": "^14.2.15",
|
||||
"@rainbow-me/rainbowkit": "^2.2.0",
|
||||
"@tanstack/react-query": "^5.55.3",
|
||||
"howler": "^2.2.4",
|
||||
"jsfxr": "^1.2.2",
|
||||
"next": "^14.2.10",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -8974,6 +8976,11 @@
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/howler": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz",
|
||||
"integrity": "sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w=="
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
@ -10117,6 +10124,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsfxr": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jsfxr/-/jsfxr-1.2.2.tgz",
|
||||
"integrity": "sha512-aBtNHZ/eJVZ3Q12HLj6F0eF20bRJTar6fjHf14zZ/Co5GzcVsEBujJO7IKwAhZS3Pe0xIvUOD3O1BoZ6ij0xhA==",
|
||||
"bin": {
|
||||
"sfxr-to-wav": "sfxr-to-wav"
|
||||
}
|
||||
},
|
||||
"node_modules/json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
"@next/eslint-plugin-next": "^14.2.15",
|
||||
"@rainbow-me/rainbowkit": "^2.2.0",
|
||||
"@tanstack/react-query": "^5.55.3",
|
||||
"howler": "^2.2.4",
|
||||
"jsfxr": "^1.2.2",
|
||||
"next": "^14.2.10",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
BIN
app/public/sounds/arcade_win.wav
Normal file
BIN
app/public/sounds/arcade_win.wav
Normal file
Binary file not shown.
BIN
app/public/sounds/lost.wav
Normal file
BIN
app/public/sounds/lost.wav
Normal file
Binary file not shown.
@ -4,30 +4,67 @@ import styles from "../styles/Modal.module.css";
|
||||
import bgStyles from "../styles/Background.module.css";
|
||||
import { bossToName, bossToReward } from "./BossInfo";
|
||||
import { bossLevelToClass } from "./Boss";
|
||||
|
||||
import { lostSound, wonSound } from "../utils/soundsEffect";
|
||||
import { useEffect } from "react";
|
||||
|
||||
interface BossOutcomeModalProps {
|
||||
setIsOpen: (val: boolean) => void,
|
||||
setIsOpen: (val: boolean) => void;
|
||||
}
|
||||
|
||||
const BossOutcomeModal = ({ setIsOpen }: BossOutcomeModalProps) => {
|
||||
const { lastBossResult } = usePlayer();
|
||||
const outcome = lastBossResult?.reward != BigInt(0);
|
||||
const ascended = lastBossResult?.prestigeGained;
|
||||
useEffect(() => {
|
||||
if (lastBossResult != null) {
|
||||
if (outcome) {
|
||||
wonSound();
|
||||
} else {
|
||||
lostSound();
|
||||
}
|
||||
}
|
||||
}, [outcome, lastBossResult])
|
||||
|
||||
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 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}`}>
|
||||
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>
|
||||
@ -36,6 +73,7 @@ const BossOutcomeModal = ({ setIsOpen }: BossOutcomeModalProps) => {
|
||||
<button onClick={() => setIsOpen(false)}>Onward!</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default BossOutcomeModal
|
||||
export default BossOutcomeModal;
|
||||
|
||||
85
app/src/components/Dashboard.tsx
Normal file
85
app/src/components/Dashboard.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from '../styles/Dashboard.module.css';
|
||||
|
||||
interface MetricsData {
|
||||
totalPlayers: number;
|
||||
totalRuns: number;
|
||||
activePlayers24h: number;
|
||||
totalBossesDefeated: number;
|
||||
totalPrestigeLevels: number;
|
||||
}
|
||||
|
||||
const SUBGRAPH_URL = "https://api.studio.thegraph.com/query/75782/slay-the-moloch-base-mainnet/version/latest";
|
||||
|
||||
const Dashboard = () => {
|
||||
const [metrics, setMetrics] = useState<MetricsData>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMetrics = async () => {
|
||||
try {
|
||||
const response = await fetch(SUBGRAPH_URL, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
query: `{
|
||||
# Get global stats
|
||||
globalStat(id: "1") {
|
||||
totalPlayers
|
||||
totalBossesDefeated
|
||||
totalPrestigeLevels
|
||||
totalRuns
|
||||
}
|
||||
# Get active players in last 24h
|
||||
players(where: { lastRaidedAt_gt: "${Math.floor(Date.now() / 1000) - 86400}" }) {
|
||||
id
|
||||
}
|
||||
}`
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
setMetrics({
|
||||
totalPlayers: parseInt(data.data.globalStat.totalPlayers),
|
||||
totalRuns: parseInt(data.data.globalStat.totalRuns),
|
||||
activePlayers24h: data.data.players.length,
|
||||
totalBossesDefeated: parseInt(data.data.globalStat.totalBossesDefeated),
|
||||
totalPrestigeLevels: parseInt(data.data.globalStat.totalPrestigeLevels)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching metrics:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMetrics();
|
||||
const interval = setInterval(fetchMetrics, 30000); // Refresh every 30 seconds
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.dashboard}>
|
||||
<div className={styles.metric}>
|
||||
<h3>Total Players</h3>
|
||||
<p>{metrics?.totalPlayers || 0}</p>
|
||||
</div>
|
||||
<div className={styles.metric}>
|
||||
<h3>Total Game Runs</h3>
|
||||
<p>{metrics?.totalRuns || 0}</p>
|
||||
</div>
|
||||
<div className={styles.metric}>
|
||||
<h3>Active Players (24h)</h3>
|
||||
<p>{metrics?.activePlayers24h || 0}</p>
|
||||
</div>
|
||||
<div className={styles.metric}>
|
||||
<h3>Total Bosses Defeated</h3>
|
||||
<p>{metrics?.totalBossesDefeated || 0}</p>
|
||||
</div>
|
||||
<div className={styles.metric}>
|
||||
<h3>Total Prestige Levels</h3>
|
||||
<p>{metrics?.totalPrestigeLevels || 0}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
@ -8,6 +8,7 @@ import Leaderboard from "./Leaderboard";
|
||||
import { usePlayer } from "../providers/PlayerProvider";
|
||||
import Boss from "./Boss";
|
||||
import BossInfo from "./BossInfo";
|
||||
import Link from "next/link";
|
||||
|
||||
const bossToMountainsClass = {
|
||||
0: styles.mountains0,
|
||||
@ -53,6 +54,7 @@ const Scene = () => {
|
||||
>
|
||||
🏆 <span className={styles.hideMobile}>Top players</span>
|
||||
</button>
|
||||
<Link href="/metrics" className={styles.metricsButton}>📈 <span className={styles.hideMobile}>Game metrics</span></Link>
|
||||
{isLeaderboardOpen && (
|
||||
<div className={styles.leaderboardOverlay}>
|
||||
<div className={styles.leaderboardContent}>
|
||||
|
||||
@ -8,8 +8,10 @@ import { RainbowKitProvider, midnightTheme } from "@rainbow-me/rainbowkit";
|
||||
import { config } from "../wagmi";
|
||||
import { Press_Start_2P, Texturina } from "next/font/google";
|
||||
import PlayerProvider from "../providers/PlayerProvider";
|
||||
import ModalProvider from '../providers/ModalProvider';
|
||||
|
||||
import ModalProvider from "../providers/ModalProvider";
|
||||
import Script from "next/script";
|
||||
import { useEffect } from "react";
|
||||
import { clickSound } from "../utils/soundsEffect";
|
||||
|
||||
const client = new QueryClient();
|
||||
const font = Texturina({ weight: ["400"], subsets: ["latin"] });
|
||||
@ -18,6 +20,18 @@ const font = Texturina({ weight: ["400"], subsets: ["latin"] });
|
||||
const fontPixel = Press_Start_2P({ weight: ["400"], subsets: ["latin"] });
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
useEffect(() => {
|
||||
window.addEventListener("click", () => {
|
||||
clickSound();
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("click", () => {
|
||||
clickSound();
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<WagmiProvider config={config}>
|
||||
<QueryClientProvider client={client}>
|
||||
@ -46,6 +60,14 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||
`}</style>
|
||||
<PlayerProvider>
|
||||
<ModalProvider>
|
||||
<Script
|
||||
src="https://sfxr.me/riffwave.js"
|
||||
strategy="beforeInteractive"
|
||||
/>
|
||||
<Script
|
||||
src="https://sfxr.me/sfxr.js"
|
||||
strategy="beforeInteractive"
|
||||
/>
|
||||
<Component {...pageProps} />
|
||||
</ModalProvider>
|
||||
</PlayerProvider>
|
||||
|
||||
15
app/src/pages/metrics.tsx
Normal file
15
app/src/pages/metrics.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import Dashboard from '../components/Dashboard';
|
||||
import styles from '../styles/Metrics.module.css';
|
||||
import Link from 'next/link';
|
||||
|
||||
const MetricsPage = () => {
|
||||
return (
|
||||
<div className={styles.metricsPage}>
|
||||
<Link href="/" className={styles.backLink}>← Back to game</Link>
|
||||
<h1>Game Metrics</h1>
|
||||
<Dashboard />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetricsPage;
|
||||
@ -1,38 +1,55 @@
|
||||
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"
|
||||
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";
|
||||
import { coinSound } from "../utils/soundsEffect";
|
||||
|
||||
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 {
|
||||
@ -44,14 +61,14 @@ export interface LastBossResult {
|
||||
}
|
||||
|
||||
export interface PlayerContextType {
|
||||
isRegistered: boolean,
|
||||
player: null | Player,
|
||||
army: null | Army,
|
||||
boss: null | Boss,
|
||||
lastBossResult: null | LastBossResult,
|
||||
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;
|
||||
}
|
||||
@ -66,166 +83,196 @@ const PlayerContext = createContext<PlayerContextType>({
|
||||
register: () => {},
|
||||
raid: () => {},
|
||||
battleWithBoss: () => {},
|
||||
addUnit: () => { }
|
||||
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.warn(error)
|
||||
}, [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,
|
||||
}
|
||||
refetchInterval: 10000,
|
||||
},
|
||||
});
|
||||
|
||||
const { data: balance, } = useReadContract({
|
||||
const { data: balance } = useReadContract({
|
||||
address: contractAddress,
|
||||
abi,
|
||||
functionName: 'balanceOf',
|
||||
functionName: "balanceOf",
|
||||
args: [address],
|
||||
query: {
|
||||
refetchInterval: 15,
|
||||
enabled: isConnected
|
||||
}
|
||||
refetchInterval: 10000,
|
||||
enabled: isConnected,
|
||||
},
|
||||
});
|
||||
|
||||
const { data: player } = useReadContract({
|
||||
address: contractAddress,
|
||||
abi,
|
||||
functionName: 'getPlayer',
|
||||
functionName: "getPlayer",
|
||||
args: [address],
|
||||
query: {
|
||||
enabled: isConnected,
|
||||
refetchInterval: 15
|
||||
}
|
||||
refetchInterval: 10000,
|
||||
},
|
||||
});
|
||||
|
||||
const { data: army } = useReadContract({
|
||||
address: contractAddress,
|
||||
abi,
|
||||
functionName: 'getArmy',
|
||||
functionName: "getArmy",
|
||||
args: [address],
|
||||
query: {
|
||||
enabled: isConnected,
|
||||
refetchInterval: 15
|
||||
}
|
||||
refetchInterval: 10000,
|
||||
},
|
||||
});
|
||||
|
||||
const { data: boss } = useReadContract({
|
||||
address: contractAddress,
|
||||
abi,
|
||||
functionName: 'getBoss',
|
||||
functionName: "getBoss",
|
||||
args: [address],
|
||||
query: {
|
||||
enabled: isConnected,
|
||||
refetchInterval: 15
|
||||
}
|
||||
refetchInterval: 10000,
|
||||
},
|
||||
});
|
||||
|
||||
const { data: lastBossResult } = useReadContract({
|
||||
address: contractAddress,
|
||||
abi,
|
||||
functionName: 'getLastBossResult',
|
||||
functionName: "getLastBossResult",
|
||||
args: [address],
|
||||
query: {
|
||||
enabled: isConnected,
|
||||
refetchInterval: 15
|
||||
}
|
||||
refetchInterval: 10000,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(balance, player, army, boss)
|
||||
|
||||
const register = useCallback((arg: "RGCVII" | "ETH") => {
|
||||
if (arg === 'ETH') {
|
||||
writeContract({
|
||||
const register = useCallback(
|
||||
(arg: "RGCVII" | "ETH") => {
|
||||
if (arg === "ETH") {
|
||||
writeContract(
|
||||
{
|
||||
abi,
|
||||
address: contractAddress,
|
||||
functionName: 'register_eth',
|
||||
functionName: "register_eth",
|
||||
value: parseEther("0.00045"),
|
||||
}, {
|
||||
onSuccess: (hash) => {
|
||||
setHashAndCallback([hash, resetHashAndCallback])
|
||||
},
|
||||
onError: () => resetHashAndCallback()
|
||||
})
|
||||
{
|
||||
onSuccess: (hash) => {
|
||||
setHashAndCallback([hash, resetHashAndCallback]);
|
||||
},
|
||||
onError: () => resetHashAndCallback(),
|
||||
}
|
||||
);
|
||||
} else if (arg === "RGCVII") {
|
||||
writeContract({
|
||||
writeContract(
|
||||
{
|
||||
abi,
|
||||
address: daoTokenAddress,
|
||||
functionName: 'approve',
|
||||
functionName: "approve",
|
||||
args: [contractAddress, parseEther("400")],
|
||||
}, {
|
||||
},
|
||||
{
|
||||
onSuccess: (hash) => {
|
||||
setHashAndCallback([
|
||||
hash,
|
||||
() => writeContract({
|
||||
() =>
|
||||
writeContract(
|
||||
{
|
||||
abi,
|
||||
address: contractAddress,
|
||||
functionName: 'register_dao',
|
||||
}, {
|
||||
functionName: "register_dao",
|
||||
},
|
||||
{
|
||||
onSuccess: (hash) => {
|
||||
setHashAndCallback([hash, resetHashAndCallback])
|
||||
setHashAndCallback([hash, resetHashAndCallback]);
|
||||
},
|
||||
onError: () => resetHashAndCallback()
|
||||
})
|
||||
])
|
||||
},
|
||||
onError: () => resetHashAndCallback()
|
||||
});
|
||||
onError: () => resetHashAndCallback(),
|
||||
}
|
||||
}, [writeContract, resetHashAndCallback])
|
||||
),
|
||||
]);
|
||||
},
|
||||
onError: () => resetHashAndCallback(),
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
[writeContract, resetHashAndCallback]
|
||||
);
|
||||
|
||||
const raid = useCallback(() => {
|
||||
writeContract({
|
||||
writeContract(
|
||||
{
|
||||
abi,
|
||||
address: contractAddress,
|
||||
functionName: 'raid',
|
||||
}, {
|
||||
onSuccess: (hash) => {
|
||||
setHashAndCallback([hash, resetHashAndCallback])
|
||||
functionName: "raid",
|
||||
},
|
||||
onError: () => resetHashAndCallback()
|
||||
})
|
||||
}, [writeContract, resetHashAndCallback])
|
||||
{
|
||||
onSuccess: (hash) => {
|
||||
setHashAndCallback([hash, resetHashAndCallback]);
|
||||
},
|
||||
onError: () => resetHashAndCallback(),
|
||||
}
|
||||
);
|
||||
}, [writeContract, resetHashAndCallback]);
|
||||
|
||||
const addUnit = useCallback((unit: UnitType) => {
|
||||
writeContract({
|
||||
const addUnit = useCallback(
|
||||
(unit: UnitType) => {
|
||||
writeContract(
|
||||
{
|
||||
abi,
|
||||
address: contractAddress,
|
||||
functionName: 'addUnit',
|
||||
args: [unit, 1]
|
||||
})
|
||||
}, [writeContract])
|
||||
functionName: "addUnit",
|
||||
args: [unit, 1],
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
coinSound();
|
||||
},
|
||||
onError: () => resetHashAndCallback(),
|
||||
}
|
||||
);
|
||||
},
|
||||
[writeContract, resetHashAndCallback]
|
||||
);
|
||||
|
||||
const battleWithBoss = useCallback(() => {
|
||||
writeContract({
|
||||
writeContract(
|
||||
{
|
||||
abi,
|
||||
address: contractAddress,
|
||||
functionName: 'battle_with_boss',
|
||||
}, {
|
||||
onSuccess: (hash) => {
|
||||
setHashAndCallback([hash, () => resetHashAndCallback()])
|
||||
functionName: "battle_with_boss",
|
||||
},
|
||||
onError: () => resetHashAndCallback()
|
||||
})
|
||||
}, [writeContract, resetHashAndCallback])
|
||||
{
|
||||
onSuccess: (hash) => {
|
||||
setHashAndCallback([hash, () => resetHashAndCallback()]);
|
||||
},
|
||||
onError: () => resetHashAndCallback(),
|
||||
}
|
||||
);
|
||||
}, [writeContract, resetHashAndCallback]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastBossResult != null) {
|
||||
@ -235,10 +282,11 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
hasFetchedLastBossFirstTime.current = true;
|
||||
}
|
||||
}
|
||||
}, [lastBossResult])
|
||||
}, [lastBossResult]);
|
||||
|
||||
return (
|
||||
<PlayerContext.Provider value={{
|
||||
<PlayerContext.Provider
|
||||
value={{
|
||||
isRegistered: isRegistered as boolean,
|
||||
player: player as Player,
|
||||
army: army as Army,
|
||||
@ -248,20 +296,26 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
register,
|
||||
raid,
|
||||
addUnit,
|
||||
battleWithBoss
|
||||
}}>
|
||||
battleWithBoss,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<div className={`${(txHash || bossBattledModalOpen) ? styles.leaderboardOverlay : ""}`}>
|
||||
<div
|
||||
className={`${
|
||||
txHash || bossBattledModalOpen ? styles.leaderboardOverlay : ""
|
||||
}`}
|
||||
>
|
||||
{txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />}
|
||||
{bossBattledModalOpen && <BossOutcomeModal setIsOpen={setBossBattlesModalOpen} />}
|
||||
{bossBattledModalOpen && (
|
||||
<BossOutcomeModal setIsOpen={setBossBattlesModalOpen} />
|
||||
)}
|
||||
</div>
|
||||
</PlayerContext.Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const usePlayer = () => {
|
||||
return useContext(PlayerContext);
|
||||
}
|
||||
|
||||
export default PlayerProvider
|
||||
};
|
||||
|
||||
export default PlayerProvider;
|
||||
|
||||
@ -402,7 +402,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.leaderboardButton {
|
||||
.leaderboardButton,
|
||||
.metricsButton {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 80px;
|
||||
@ -425,6 +426,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.metricsButton {
|
||||
left: auto;
|
||||
top: auto;
|
||||
right: 32px;
|
||||
bottom: 32px;
|
||||
}
|
||||
|
||||
.leaderboardButton:hover {
|
||||
transform: scale(1.1);
|
||||
|
||||
29
app/src/styles/Dashboard.module.css
Normal file
29
app/src/styles/Dashboard.module.css
Normal file
@ -0,0 +1,29 @@
|
||||
.dashboard {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
padding: 2rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.metric {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.metric h3 {
|
||||
color: #888;
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.metric p {
|
||||
color: #fff;
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
23
app/src/styles/Metrics.module.css
Normal file
23
app/src/styles/Metrics.module.css
Normal file
@ -0,0 +1,23 @@
|
||||
.metricsPage {
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
background: #1a1a1a;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.metricsPage h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.backLink {
|
||||
display: inline-block;
|
||||
color: #888;
|
||||
text-decoration: none;
|
||||
margin-bottom: 2rem;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.backLink:hover {
|
||||
color: white;
|
||||
}
|
||||
44
app/src/utils/soundsEffect.js
Normal file
44
app/src/utils/soundsEffect.js
Normal file
@ -0,0 +1,44 @@
|
||||
// https://sfxr.me/
|
||||
// https://github.com/chr15m/jsfxr?tab=readme-ov-file#library
|
||||
// https://github.com/goldfire/howler.js
|
||||
|
||||
import { sfxr } from "jsfxr";
|
||||
import { Howl } from "howler";
|
||||
|
||||
export const coinSound = () => {
|
||||
const coin = sfxr.toAudio(
|
||||
"34T6PkpUzHPSs4CvSaNXpQ6fftpW1yrCDSda6oECJ5CH6BEaokmoZC7aAra2xef61iP6srxUaRZUk8Z2DRJHNMEvtWCLjgKkUCFtpxPe9o8AvYBCJZG1YNNPR"
|
||||
);
|
||||
coin.play();
|
||||
};
|
||||
|
||||
export const errorSound = () => {
|
||||
const fail = sfxr.toAudio(
|
||||
"3mLuemG9nym33ak7ot6gTcNTFBBnNkcF4rmQFkh1zRhrvJ6totmE1EX61m9LTW9KWGuQpQEMqnVopubShwmxqQK7vAZYMXKbJCxYE9bcTh2qMm9JbMRJAKD5a"
|
||||
);
|
||||
fail.play();
|
||||
};
|
||||
|
||||
export const clickSound = () => {
|
||||
const click = sfxr.toAudio(
|
||||
"7BMHBGPkXasqBZ54qHeMQTKSwDs2Y176H4hQVNkvQPg5eZyckEhyzKTnAZfqnp9ayL5iPVRNXFNjXAXBbUKhT7U6c1hKZBgWzaWkWTQvmcrCwikKi3RoF7wbd"
|
||||
);
|
||||
click.play();
|
||||
};
|
||||
|
||||
export const lostSound = () => {
|
||||
const fail = new Howl({
|
||||
src: ["/sounds/lost.wav"],
|
||||
volume: 0.7
|
||||
});
|
||||
|
||||
fail.play();
|
||||
};
|
||||
|
||||
export const wonSound = () => {
|
||||
const won = new Howl({
|
||||
src: ["/sounds/arcade_win.wav"],
|
||||
});
|
||||
|
||||
won.play();
|
||||
};
|
||||
@ -9,9 +9,9 @@ export const config = getDefaultConfig({
|
||||
appName: 'RainbowKit App',
|
||||
projectId: 'YOUR_PROJECT_ID',
|
||||
chains: [
|
||||
baseSepolia,
|
||||
foundry,
|
||||
base,
|
||||
foundry,
|
||||
baseSepolia,
|
||||
],
|
||||
ssr: true,
|
||||
});
|
||||
|
||||
73
broadcast/RaidGeld.s.sol/8453/run-1730464724.json
Normal file
73
broadcast/RaidGeld.s.sol/8453/run-1730464724.json
Normal file
File diff suppressed because one or more lines are too long
73
broadcast/RaidGeld.s.sol/8453/run-latest.json
Normal file
73
broadcast/RaidGeld.s.sol/8453/run-latest.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user