Compare commits
3 Commits
main
...
session-wa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e691e46af | ||
|
|
4c8f2250b7 | ||
|
|
ef1c242471 |
15
app/package-lock.json
generated
15
app/package-lock.json
generated
@ -11,8 +11,6 @@
|
|||||||
"@next/eslint-plugin-next": "^14.2.15",
|
"@next/eslint-plugin-next": "^14.2.15",
|
||||||
"@rainbow-me/rainbowkit": "^2.2.0",
|
"@rainbow-me/rainbowkit": "^2.2.0",
|
||||||
"@tanstack/react-query": "^5.55.3",
|
"@tanstack/react-query": "^5.55.3",
|
||||||
"howler": "^2.2.4",
|
|
||||||
"jsfxr": "^1.2.2",
|
|
||||||
"next": "^14.2.10",
|
"next": "^14.2.10",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@ -8976,11 +8974,6 @@
|
|||||||
"minimalistic-crypto-utils": "^1.0.1"
|
"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": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||||
@ -10124,14 +10117,6 @@
|
|||||||
"node": ">=6"
|
"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": {
|
"node_modules/json-buffer": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||||
|
|||||||
@ -12,8 +12,6 @@
|
|||||||
"@next/eslint-plugin-next": "^14.2.15",
|
"@next/eslint-plugin-next": "^14.2.15",
|
||||||
"@rainbow-me/rainbowkit": "^2.2.0",
|
"@rainbow-me/rainbowkit": "^2.2.0",
|
||||||
"@tanstack/react-query": "^5.55.3",
|
"@tanstack/react-query": "^5.55.3",
|
||||||
"howler": "^2.2.4",
|
|
||||||
"jsfxr": "^1.2.2",
|
|
||||||
"next": "^14.2.10",
|
"next": "^14.2.10",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -4,67 +4,30 @@ import styles from "../styles/Modal.module.css";
|
|||||||
import bgStyles from "../styles/Background.module.css";
|
import bgStyles from "../styles/Background.module.css";
|
||||||
import { bossToName, bossToReward } from "./BossInfo";
|
import { bossToName, bossToReward } from "./BossInfo";
|
||||||
import { bossLevelToClass } from "./Boss";
|
import { bossLevelToClass } from "./Boss";
|
||||||
import { lostSound, wonSound } from "../utils/soundsEffect";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
interface BossOutcomeModalProps {
|
interface BossOutcomeModalProps {
|
||||||
setIsOpen: (val: boolean) => void;
|
setIsOpen: (val: boolean) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
const BossOutcomeModal = ({ setIsOpen }: BossOutcomeModalProps) => {
|
const BossOutcomeModal = ({ setIsOpen }: BossOutcomeModalProps) => {
|
||||||
const { lastBossResult } = usePlayer();
|
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;
|
if (lastBossResult == null) return null;
|
||||||
|
|
||||||
const text = outcome ? (
|
const outcome = lastBossResult.reward != BigInt(0);
|
||||||
<span>
|
const ascended = lastBossResult.prestigeGained;
|
||||||
and you <strong className={styles.won}>won!</strong> 🤩
|
|
||||||
</span>
|
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));
|
||||||
<span>
|
const rewardText =
|
||||||
and you <strong className={styles.lost}>lost</strong> 😔
|
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>
|
||||||
</span>
|
: 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 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 bossName = bossToName[lastBossResult.variant];
|
||||||
const bossClass = bossLevelToClass[lastBossResult.variant];
|
const bossClass = bossLevelToClass[lastBossResult.variant];
|
||||||
|
|
||||||
return (
|
return <div className={`${styles.modal} ${styles.bossModal}`}>
|
||||||
<div className={`${styles.modal} ${styles.bossModal}`}>
|
|
||||||
<h2>You battled {bossName} Moloch!</h2>
|
<h2>You battled {bossName} Moloch!</h2>
|
||||||
<div className={`${bgStyles.boss} ${bossClass} ${styles.image}`} />
|
<div className={`${bgStyles.boss} ${bossClass} ${styles.image}`} />
|
||||||
<p className={styles.outcome}>{text}</p>
|
<p className={styles.outcome}>{text}</p>
|
||||||
@ -73,7 +36,6 @@ const BossOutcomeModal = ({ setIsOpen }: BossOutcomeModalProps) => {
|
|||||||
<button onClick={() => setIsOpen(false)}>Onward!</button>
|
<button onClick={() => setIsOpen(false)}>Onward!</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export default BossOutcomeModal;
|
export default BossOutcomeModal
|
||||||
|
|||||||
@ -1,85 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,31 +1,22 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from 'react'
|
||||||
import styles from "../styles/Leaderboard.module.css";
|
import styles from '../styles/Leaderboard.module.css'
|
||||||
import {
|
import { TopEarnersResponse, TopRaidersResponse } from '../types/leaderboard'
|
||||||
TopEarnersResponse,
|
import { formatUnits } from 'viem'
|
||||||
TopRaidersResponse,
|
|
||||||
PlayerResponse,
|
|
||||||
} from "../types/leaderboard";
|
|
||||||
import { formatUnits } from "viem";
|
|
||||||
|
|
||||||
const SUBGRAPH_URL =
|
const SUBGRAPH_URL = 'https://api.studio.thegraph.com/query/75782/slay-the-moloch-base-sepolia/version/latest'
|
||||||
"https://api.studio.thegraph.com/query/75782/slay-the-moloch-base-mainnet/version/latest";
|
|
||||||
|
|
||||||
const Leaderboard = () => {
|
const Leaderboard = () => {
|
||||||
const [topEarners, setTopEarners] = useState<TopEarnersResponse>();
|
const [topEarners, setTopEarners] = useState<TopEarnersResponse>()
|
||||||
const [topRaiders, setTopRaiders] = useState<TopRaidersResponse>();
|
const [topRaiders, setTopRaiders] = useState<TopRaidersResponse>()
|
||||||
const [activeTab, setActiveTab] = useState<
|
const [activeTab, setActiveTab] = useState<'earners' | 'raiders'>('earners')
|
||||||
"earners" | "raiders" | "bosses" | "prestige"
|
|
||||||
>("earners");
|
|
||||||
const [bossesDefeated, setBossesDefeated] = useState<PlayerResponse>();
|
|
||||||
const [playerPrestige, setPlayerPrestige] = useState<PlayerResponse>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchLeaderboards = async () => {
|
const fetchLeaderboards = async () => {
|
||||||
try {
|
try {
|
||||||
// Fetch top earners
|
// Fetch top earners
|
||||||
const earnersResponse = await fetch(SUBGRAPH_URL, {
|
const earnersResponse = await fetch(SUBGRAPH_URL, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
query: `{
|
query: `{
|
||||||
armies(first: 10, orderBy: profitPerSecond, orderDirection: desc) {
|
armies(first: 10, orderBy: profitPerSecond, orderDirection: desc) {
|
||||||
@ -41,16 +32,16 @@ const Leaderboard = () => {
|
|||||||
anointedLevel
|
anointedLevel
|
||||||
championLevel
|
championLevel
|
||||||
}
|
}
|
||||||
}`,
|
}`
|
||||||
}),
|
})
|
||||||
});
|
})
|
||||||
const earnersData = await earnersResponse.json();
|
const earnersData = await earnersResponse.json()
|
||||||
setTopEarners({ armies: earnersData.data.armies });
|
setTopEarners({ armies: earnersData.data.armies })
|
||||||
|
|
||||||
// Fetch top raiders
|
// Fetch top raiders
|
||||||
const raidersResponse = await fetch(SUBGRAPH_URL, {
|
const raidersResponse = await fetch(SUBGRAPH_URL, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
query: `{
|
query: `{
|
||||||
players(first: 10, orderBy: numberOfRaids, orderDirection: desc) {
|
players(first: 10, orderBy: numberOfRaids, orderDirection: desc) {
|
||||||
@ -59,52 +50,20 @@ const Leaderboard = () => {
|
|||||||
totalMinted
|
totalMinted
|
||||||
currentBalance
|
currentBalance
|
||||||
}
|
}
|
||||||
}`,
|
}`
|
||||||
}),
|
})
|
||||||
});
|
})
|
||||||
const raidersData = await raidersResponse.json();
|
const raidersData = await raidersResponse.json()
|
||||||
setTopRaiders({ players: raidersData.data.players });
|
setTopRaiders({ players: raidersData.data.players })
|
||||||
|
|
||||||
// Fetch bosses defeated
|
|
||||||
const bossesDefeatedResponse = await fetch(SUBGRAPH_URL, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
query: `{
|
|
||||||
players(first: 10, orderBy: bossesDefeated, orderDirection: desc) {
|
|
||||||
id
|
|
||||||
bossesDefeated
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const bossesDefeatedData = await bossesDefeatedResponse.json();
|
|
||||||
setBossesDefeated({ players: bossesDefeatedData.data.players });
|
|
||||||
|
|
||||||
// Fetch player prestige
|
|
||||||
const playerPrestigeResponse = await fetch(SUBGRAPH_URL, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
query: `{
|
|
||||||
players(first: 10, orderBy: prestigeLevel, orderDirection: desc) {
|
|
||||||
id
|
|
||||||
prestigeLevel
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const playerPrestigeData = await playerPrestigeResponse.json();
|
|
||||||
setPlayerPrestige({ players: playerPrestigeData.data.players });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching leaderboard:", error);
|
console.error('Error fetching leaderboard:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
fetchLeaderboards();
|
fetchLeaderboards()
|
||||||
const interval = setInterval(fetchLeaderboards, 30000); // Refresh every 30 seconds
|
const interval = setInterval(fetchLeaderboards, 30000) // Refresh every 30 seconds
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval)
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.leaderboard}>
|
<div className={styles.leaderboard}>
|
||||||
@ -112,106 +71,44 @@ const Leaderboard = () => {
|
|||||||
|
|
||||||
<div className={styles.tabs}>
|
<div className={styles.tabs}>
|
||||||
<button
|
<button
|
||||||
className={`${styles.tab} ${
|
className={`${styles.tab} ${activeTab === 'earners' ? styles.active : ''}`}
|
||||||
activeTab === "earners" ? styles.active : ""
|
onClick={() => setActiveTab('earners')}
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab("earners")}
|
|
||||||
>
|
>
|
||||||
Top Earners
|
Top Earners
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`${styles.tab} ${
|
className={`${styles.tab} ${activeTab === 'raiders' ? styles.active : ''}`}
|
||||||
activeTab === "raiders" ? styles.active : ""
|
onClick={() => setActiveTab('raiders')}
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab("raiders")}
|
|
||||||
>
|
>
|
||||||
Top Raiders
|
Top Raiders
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
className={`${styles.tab} ${
|
|
||||||
activeTab === "bosses" ? styles.active : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab("bosses")}
|
|
||||||
>
|
|
||||||
Top Boss Slayers
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`${styles.tab} ${
|
|
||||||
activeTab === "prestige" ? styles.active : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab("prestige")}
|
|
||||||
>
|
|
||||||
Top Players by Prestige
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activeTab === "earners" && (
|
{activeTab === 'earners' && (
|
||||||
<div className={styles.list}>
|
<div className={styles.list}>
|
||||||
{topEarners?.armies.map((army, index) => (
|
{topEarners?.armies.map((army, index) => (
|
||||||
<div key={army.player.id} className={styles.item}>
|
<div key={army.player.id} className={styles.item}>
|
||||||
<span className={styles.rank}>#{index + 1}</span>
|
<span className={styles.rank}>#{index + 1}</span>
|
||||||
<span className={styles.address}>
|
<span className={styles.address}>{army.player.id.slice(0, 6)}...{army.player.id.slice(-4)}</span>
|
||||||
{army.player.id.slice(0, 6)}...
|
<span className={styles.stat}>{formatUnits(BigInt(army.profitPerSecond), 4)} GELD/s</span>
|
||||||
{army.player.id.slice(-4)}
|
|
||||||
</span>
|
|
||||||
<span className={styles.stat}>
|
|
||||||
{formatUnits(BigInt(army.profitPerSecond), 4)}{" "}
|
|
||||||
GELD/s
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "raiders" && (
|
{activeTab === 'raiders' && (
|
||||||
<div className={styles.list}>
|
<div className={styles.list}>
|
||||||
{topRaiders?.players.map((player, index) => (
|
{topRaiders?.players.map((player, index) => (
|
||||||
<div key={player.id} className={styles.item}>
|
<div key={player.id} className={styles.item}>
|
||||||
<span className={styles.rank}>#{index + 1}</span>
|
<span className={styles.rank}>#{index + 1}</span>
|
||||||
<span className={styles.address}>
|
<span className={styles.address}>{player.id.slice(0, 6)}...{player.id.slice(-4)}</span>
|
||||||
{player.id.slice(0, 6)}...{player.id.slice(-4)}
|
<span className={styles.stat}>{player.numberOfRaids} raids</span>
|
||||||
</span>
|
|
||||||
<span className={styles.stat}>
|
|
||||||
{player.numberOfRaids} raids
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === "bosses" && (
|
|
||||||
<div className={styles.list}>
|
|
||||||
{bossesDefeated?.players.map((player, index) => (
|
|
||||||
<div key={player.id} className={styles.item}>
|
|
||||||
<span className={styles.rank}>#{index + 1}</span>
|
|
||||||
<span className={styles.address}>
|
|
||||||
{player.id.slice(0, 6)}...{player.id.slice(-4)}
|
|
||||||
</span>
|
|
||||||
<span className={styles.stat}>
|
|
||||||
{player.bossesDefeated} Bosses Slain
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === "prestige" && (
|
|
||||||
<div className={styles.list}>
|
|
||||||
{playerPrestige?.players.map((player, index) => (
|
|
||||||
<div key={player.id} className={styles.item}>
|
|
||||||
<span className={styles.rank}>#{index + 1}</span>
|
|
||||||
<span className={styles.address}>
|
|
||||||
{player.id.slice(0, 6)}...{player.id.slice(-4)}
|
|
||||||
</span>
|
|
||||||
<span className={styles.stat}>
|
|
||||||
{player.prestigeLevel} Prestige Level
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Leaderboard;
|
export default Leaderboard
|
||||||
@ -8,7 +8,6 @@ import Leaderboard from "./Leaderboard";
|
|||||||
import { usePlayer } from "../providers/PlayerProvider";
|
import { usePlayer } from "../providers/PlayerProvider";
|
||||||
import Boss from "./Boss";
|
import Boss from "./Boss";
|
||||||
import BossInfo from "./BossInfo";
|
import BossInfo from "./BossInfo";
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
const bossToMountainsClass = {
|
const bossToMountainsClass = {
|
||||||
0: styles.mountains0,
|
0: styles.mountains0,
|
||||||
@ -54,7 +53,6 @@ const Scene = () => {
|
|||||||
>
|
>
|
||||||
🏆 <span className={styles.hideMobile}>Top players</span>
|
🏆 <span className={styles.hideMobile}>Top players</span>
|
||||||
</button>
|
</button>
|
||||||
<Link href="/metrics" className={styles.metricsButton}>📈 <span className={styles.hideMobile}>Game metrics</span></Link>
|
|
||||||
{isLeaderboardOpen && (
|
{isLeaderboardOpen && (
|
||||||
<div className={styles.leaderboardOverlay}>
|
<div className={styles.leaderboardOverlay}>
|
||||||
<div className={styles.leaderboardContent}>
|
<div className={styles.leaderboardContent}>
|
||||||
|
|||||||
@ -8,10 +8,8 @@ import { RainbowKitProvider, midnightTheme } from "@rainbow-me/rainbowkit";
|
|||||||
import { config } from "../wagmi";
|
import { config } from "../wagmi";
|
||||||
import { Press_Start_2P, Texturina } from "next/font/google";
|
import { Press_Start_2P, Texturina } from "next/font/google";
|
||||||
import PlayerProvider from "../providers/PlayerProvider";
|
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 client = new QueryClient();
|
||||||
const font = Texturina({ weight: ["400"], subsets: ["latin"] });
|
const font = Texturina({ weight: ["400"], subsets: ["latin"] });
|
||||||
@ -20,18 +18,6 @@ const font = Texturina({ weight: ["400"], subsets: ["latin"] });
|
|||||||
const fontPixel = Press_Start_2P({ weight: ["400"], subsets: ["latin"] });
|
const fontPixel = Press_Start_2P({ weight: ["400"], subsets: ["latin"] });
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener("click", () => {
|
|
||||||
clickSound();
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("click", () => {
|
|
||||||
clickSound();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WagmiProvider config={config}>
|
<WagmiProvider config={config}>
|
||||||
<QueryClientProvider client={client}>
|
<QueryClientProvider client={client}>
|
||||||
@ -60,14 +46,6 @@ function MyApp({ Component, pageProps }: AppProps) {
|
|||||||
`}</style>
|
`}</style>
|
||||||
<PlayerProvider>
|
<PlayerProvider>
|
||||||
<ModalProvider>
|
<ModalProvider>
|
||||||
<Script
|
|
||||||
src="https://sfxr.me/riffwave.js"
|
|
||||||
strategy="beforeInteractive"
|
|
||||||
/>
|
|
||||||
<Script
|
|
||||||
src="https://sfxr.me/sfxr.js"
|
|
||||||
strategy="beforeInteractive"
|
|
||||||
/>
|
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</PlayerProvider>
|
</PlayerProvider>
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
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,55 +1,38 @@
|
|||||||
import React, {
|
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||||
createContext,
|
import { useAccount, useReadContract, useWriteContract } from 'wagmi'
|
||||||
ReactNode,
|
import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json"
|
||||||
useCallback,
|
import { Hash, parseEther } from 'viem'
|
||||||
useContext,
|
import contracts from '../../contract_address'
|
||||||
useEffect,
|
import WaitingForTxModal from '../components/WaitingForTxModal'
|
||||||
useRef,
|
import BossOutcomeModal from '../components/BossOutcomeModal'
|
||||||
useState,
|
import styles from "../styles/Background.module.css"
|
||||||
} 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 { contractAddress, daoTokenAddress } = contracts
|
||||||
const abi = contractAbi.abi;
|
const abi = contractAbi.abi
|
||||||
|
|
||||||
export type UnitType = 0 | 1 | 2 | 3;
|
export type UnitType = 0 | 1 | 2 | 3
|
||||||
export type BossLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
export type BossLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6
|
||||||
|
|
||||||
export interface Player {
|
export interface Player {
|
||||||
created_at: bigint;
|
created_at: bigint,
|
||||||
last_raided_at: bigint;
|
last_raided_at: bigint,
|
||||||
total_minted: bigint;
|
total_minted: bigint
|
||||||
total_rewards: bigint;
|
total_rewards: bigint,
|
||||||
n_runs: number;
|
n_runs: number,
|
||||||
prestige_level: number;
|
prestige_level: number,
|
||||||
is_registered: boolean;
|
is_registered: boolean,
|
||||||
has_active_session: boolean;
|
has_active_session: boolean,
|
||||||
}
|
}
|
||||||
export interface Army {
|
export interface Army {
|
||||||
anointed: { level: number };
|
anointed: { level: number }
|
||||||
apprentice: { level: number };
|
apprentice: { level: number }
|
||||||
champion: { level: number };
|
champion: { level: number }
|
||||||
moloch_denier: { level: number };
|
moloch_denier: { level: number }
|
||||||
profit_per_second: bigint;
|
profit_per_second: bigint
|
||||||
}
|
}
|
||||||
export interface Boss {
|
export interface Boss {
|
||||||
level: BossLevel;
|
level: BossLevel;
|
||||||
variants: [
|
variants: [BossLevel, BossLevel, BossLevel, BossLevel, BossLevel, BossLevel, BossLevel]
|
||||||
BossLevel,
|
|
||||||
BossLevel,
|
|
||||||
BossLevel,
|
|
||||||
BossLevel,
|
|
||||||
BossLevel,
|
|
||||||
BossLevel,
|
|
||||||
BossLevel
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LastBossResult {
|
export interface LastBossResult {
|
||||||
@ -61,14 +44,14 @@ export interface LastBossResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
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, amount?: number) => void;
|
addUnit: (unit: UnitType, amount?: number) => void;
|
||||||
}
|
}
|
||||||
@ -83,196 +66,166 @@ const PlayerContext = createContext<PlayerContextType>({
|
|||||||
register: () => { },
|
register: () => { },
|
||||||
raid: () => { },
|
raid: () => { },
|
||||||
battleWithBoss: () => { },
|
battleWithBoss: () => { },
|
||||||
addUnit: () => {},
|
addUnit: () => { }
|
||||||
});
|
});
|
||||||
|
|
||||||
const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
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<
|
const [[txHash, callbackFn], setHashAndCallback] = useState<[Hash | null, () => void]>([null, () => { }])
|
||||||
[Hash | null, () => void]
|
|
||||||
>([null, () => {}]);
|
|
||||||
const [bossBattledModalOpen, setBossBattlesModalOpen] = useState(false);
|
const [bossBattledModalOpen, setBossBattlesModalOpen] = useState(false);
|
||||||
const hasFetchedLastBossFirstTime = useRef(false);
|
const hasFetchedLastBossFirstTime = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.warn(error);
|
console.warn(error)
|
||||||
}, [error]);
|
}, [error])
|
||||||
|
|
||||||
const resetHashAndCallback = useCallback(() => {
|
const resetHashAndCallback = useCallback(() => {
|
||||||
setHashAndCallback([null, () => {}]);
|
setHashAndCallback([null, () => { }])
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
const { data: isRegistered } = useReadContract({
|
const { data: isRegistered } = useReadContract({
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
abi,
|
abi,
|
||||||
functionName: "isRegistered",
|
functionName: 'isRegistered',
|
||||||
args: [address],
|
args: [address],
|
||||||
query: {
|
query: {
|
||||||
enabled: isConnected,
|
enabled: isConnected,
|
||||||
refetchInterval: 10000,
|
refetchInterval: 15,
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: balance } = useReadContract({
|
const { data: balance, } = useReadContract({
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
abi,
|
abi,
|
||||||
functionName: "balanceOf",
|
functionName: 'balanceOf',
|
||||||
args: [address],
|
args: [address],
|
||||||
query: {
|
query: {
|
||||||
refetchInterval: 10000,
|
refetchInterval: 15,
|
||||||
enabled: isConnected,
|
enabled: isConnected
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: player } = useReadContract({
|
const { data: player } = useReadContract({
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
abi,
|
abi,
|
||||||
functionName: "getPlayer",
|
functionName: 'getPlayer',
|
||||||
args: [address],
|
args: [address],
|
||||||
query: {
|
query: {
|
||||||
enabled: isConnected,
|
enabled: isConnected,
|
||||||
refetchInterval: 10000,
|
refetchInterval: 15
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: army } = useReadContract({
|
const { data: army } = useReadContract({
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
abi,
|
abi,
|
||||||
functionName: "getArmy",
|
functionName: 'getArmy',
|
||||||
args: [address],
|
args: [address],
|
||||||
query: {
|
query: {
|
||||||
enabled: isConnected,
|
enabled: isConnected,
|
||||||
refetchInterval: 10000,
|
refetchInterval: 15
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: boss } = useReadContract({
|
const { data: boss } = useReadContract({
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
abi,
|
abi,
|
||||||
functionName: "getBoss",
|
functionName: 'getBoss',
|
||||||
args: [address],
|
args: [address],
|
||||||
query: {
|
query: {
|
||||||
enabled: isConnected,
|
enabled: isConnected,
|
||||||
refetchInterval: 10000,
|
refetchInterval: 15
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: lastBossResult } = useReadContract({
|
const { data: lastBossResult } = useReadContract({
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
abi,
|
abi,
|
||||||
functionName: "getLastBossResult",
|
functionName: 'getLastBossResult',
|
||||||
args: [address],
|
args: [address],
|
||||||
query: {
|
query: {
|
||||||
enabled: isConnected,
|
enabled: isConnected,
|
||||||
refetchInterval: 10000,
|
refetchInterval: 15
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const register = useCallback(
|
console.log(balance, player, army, boss)
|
||||||
(arg: "RGCVII" | "ETH") => {
|
|
||||||
if (arg === "ETH") {
|
const register = useCallback((arg: "RGCVII" | "ETH") => {
|
||||||
writeContract(
|
if (arg === 'ETH') {
|
||||||
{
|
writeContract({
|
||||||
abi,
|
abi,
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
functionName: "register_eth",
|
functionName: 'register_eth',
|
||||||
value: parseEther("0.00045"),
|
value: parseEther("0.00045"),
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
onSuccess: (hash) => {
|
onSuccess: (hash) => {
|
||||||
setHashAndCallback([hash, resetHashAndCallback]);
|
setHashAndCallback([hash, resetHashAndCallback])
|
||||||
},
|
},
|
||||||
onError: () => resetHashAndCallback(),
|
onError: () => resetHashAndCallback()
|
||||||
}
|
})
|
||||||
);
|
|
||||||
} else if (arg === "RGCVII") {
|
} else if (arg === "RGCVII") {
|
||||||
writeContract(
|
writeContract({
|
||||||
{
|
|
||||||
abi,
|
abi,
|
||||||
address: daoTokenAddress,
|
address: daoTokenAddress,
|
||||||
functionName: "approve",
|
functionName: 'approve',
|
||||||
args: [contractAddress, parseEther("400")],
|
args: [contractAddress, parseEther("400")],
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
onSuccess: (hash) => {
|
onSuccess: (hash) => {
|
||||||
setHashAndCallback([
|
setHashAndCallback([
|
||||||
hash,
|
hash,
|
||||||
() =>
|
() => writeContract({
|
||||||
writeContract(
|
|
||||||
{
|
|
||||||
abi,
|
abi,
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
functionName: "register_dao",
|
functionName: 'register_dao',
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
onSuccess: (hash) => {
|
onSuccess: (hash) => {
|
||||||
setHashAndCallback([hash, resetHashAndCallback]);
|
setHashAndCallback([hash, resetHashAndCallback])
|
||||||
},
|
},
|
||||||
onError: () => resetHashAndCallback(),
|
onError: () => resetHashAndCallback()
|
||||||
}
|
})
|
||||||
),
|
])
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
onError: () => resetHashAndCallback(),
|
onError: () => resetHashAndCallback()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
}, [writeContract, resetHashAndCallback])
|
||||||
}
|
|
||||||
},
|
|
||||||
[writeContract, resetHashAndCallback]
|
|
||||||
);
|
|
||||||
|
|
||||||
const raid = useCallback(() => {
|
const raid = useCallback(() => {
|
||||||
writeContract(
|
writeContract({
|
||||||
{
|
|
||||||
abi,
|
abi,
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
functionName: "raid",
|
functionName: 'raid',
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
onSuccess: (hash) => {
|
onSuccess: (hash) => {
|
||||||
setHashAndCallback([hash, resetHashAndCallback]);
|
setHashAndCallback([hash, resetHashAndCallback])
|
||||||
},
|
},
|
||||||
onError: () => resetHashAndCallback(),
|
onError: () => resetHashAndCallback()
|
||||||
}
|
})
|
||||||
);
|
}, [writeContract, resetHashAndCallback])
|
||||||
}, [writeContract, resetHashAndCallback]);
|
|
||||||
|
|
||||||
const addUnit = useCallback(
|
const addUnit = useCallback((unit: UnitType) => {
|
||||||
(unit: UnitType) => {
|
writeContract({
|
||||||
writeContract(
|
|
||||||
{
|
|
||||||
abi,
|
abi,
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
functionName: "addUnit",
|
functionName: 'addUnit',
|
||||||
args: [unit, 1],
|
args: [unit, 1]
|
||||||
},
|
})
|
||||||
{
|
}, [writeContract])
|
||||||
onSuccess: () => {
|
|
||||||
coinSound();
|
|
||||||
},
|
|
||||||
onError: () => resetHashAndCallback(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[writeContract, resetHashAndCallback]
|
|
||||||
);
|
|
||||||
|
|
||||||
const battleWithBoss = useCallback(() => {
|
const battleWithBoss = useCallback(() => {
|
||||||
writeContract(
|
writeContract({
|
||||||
{
|
|
||||||
abi,
|
abi,
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
functionName: "battle_with_boss",
|
functionName: 'battle_with_boss',
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
onSuccess: (hash) => {
|
onSuccess: (hash) => {
|
||||||
setHashAndCallback([hash, () => resetHashAndCallback()]);
|
setHashAndCallback([hash, () => resetHashAndCallback()])
|
||||||
},
|
},
|
||||||
onError: () => resetHashAndCallback(),
|
onError: () => resetHashAndCallback()
|
||||||
}
|
})
|
||||||
);
|
}, [writeContract, resetHashAndCallback])
|
||||||
}, [writeContract, resetHashAndCallback]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastBossResult != null) {
|
if (lastBossResult != null) {
|
||||||
@ -282,11 +235,10 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
hasFetchedLastBossFirstTime.current = true;
|
hasFetchedLastBossFirstTime.current = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [lastBossResult]);
|
}, [lastBossResult])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlayerContext.Provider
|
<PlayerContext.Provider value={{
|
||||||
value={{
|
|
||||||
isRegistered: isRegistered as boolean,
|
isRegistered: isRegistered as boolean,
|
||||||
player: player as Player,
|
player: player as Player,
|
||||||
army: army as Army,
|
army: army as Army,
|
||||||
@ -296,26 +248,20 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
register,
|
register,
|
||||||
raid,
|
raid,
|
||||||
addUnit,
|
addUnit,
|
||||||
battleWithBoss,
|
battleWithBoss
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
<div
|
<div className={`${(txHash || bossBattledModalOpen) ? styles.leaderboardOverlay : ""}`}>
|
||||||
className={`${
|
|
||||||
txHash || bossBattledModalOpen ? styles.leaderboardOverlay : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />}
|
{txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />}
|
||||||
{bossBattledModalOpen && (
|
{bossBattledModalOpen && <BossOutcomeModal setIsOpen={setBossBattlesModalOpen} />}
|
||||||
<BossOutcomeModal setIsOpen={setBossBattlesModalOpen} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</PlayerContext.Provider>
|
</PlayerContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const usePlayer = () => {
|
export const usePlayer = () => {
|
||||||
return useContext(PlayerContext);
|
return useContext(PlayerContext);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export default PlayerProvider
|
||||||
|
|
||||||
export default PlayerProvider;
|
|
||||||
|
|||||||
@ -402,8 +402,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaderboardButton,
|
.leaderboardButton {
|
||||||
.metricsButton {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 30px;
|
top: 30px;
|
||||||
left: 80px;
|
left: 80px;
|
||||||
@ -426,12 +425,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.metricsButton {
|
|
||||||
left: auto;
|
|
||||||
top: auto;
|
|
||||||
right: 32px;
|
|
||||||
bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaderboardButton:hover {
|
.leaderboardButton:hover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
.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;
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
.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;
|
|
||||||
}
|
|
||||||
@ -1,31 +1,25 @@
|
|||||||
export interface Player {
|
export interface Player {
|
||||||
id: string;
|
id: string
|
||||||
totalMinted: string;
|
totalMinted: string
|
||||||
currentBalance: string;
|
currentBalance: string
|
||||||
numberOfRaids: string;
|
numberOfRaids: string
|
||||||
army?: Army;
|
army?: Army
|
||||||
bossesDefeated?: string;
|
|
||||||
prestigeLevel: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Army {
|
export interface Army {
|
||||||
player: Player;
|
player: Player
|
||||||
profitPerSecond: string;
|
profitPerSecond: string
|
||||||
projectedDailyEarnings: string;
|
projectedDailyEarnings: string
|
||||||
molochDenierLevel: string;
|
molochDenierLevel: string
|
||||||
apprenticeLevel: string;
|
apprenticeLevel: string
|
||||||
anointedLevel: string;
|
anointedLevel: string
|
||||||
championLevel: string;
|
championLevel: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TopEarnersResponse {
|
export interface TopEarnersResponse {
|
||||||
armies: Army[];
|
armies: Army[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TopRaidersResponse {
|
export interface TopRaidersResponse {
|
||||||
players: Player[];
|
players: Player[]
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlayerResponse {
|
|
||||||
players: Player[];
|
|
||||||
}
|
}
|
||||||
@ -1,44 +0,0 @@
|
|||||||
// 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',
|
appName: 'RainbowKit App',
|
||||||
projectId: 'YOUR_PROJECT_ID',
|
projectId: 'YOUR_PROJECT_ID',
|
||||||
chains: [
|
chains: [
|
||||||
base,
|
|
||||||
foundry,
|
|
||||||
baseSepolia,
|
baseSepolia,
|
||||||
|
foundry,
|
||||||
|
base,
|
||||||
],
|
],
|
||||||
ssr: true,
|
ssr: true,
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -20,11 +20,16 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
uint256 public BUY_IN_DAO_TOKEN_AMOUNT;
|
uint256 public BUY_IN_DAO_TOKEN_AMOUNT;
|
||||||
uint256 public constant INITIAL_GELD = 50 * MANTISSA;
|
uint256 public constant INITIAL_GELD = 50 * MANTISSA;
|
||||||
uint256 public constant SACRIFICE_SHARE = 1e3; // 10%
|
uint256 public constant SACRIFICE_SHARE = 1e3; // 10%
|
||||||
|
uint256 public constant SESSION_WALLET_FUNDING_CAP = 0.001 ether;
|
||||||
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;
|
mapping(address => LastBossResult) private lastBossResults;
|
||||||
|
|
||||||
|
mapping(address => address) private playerToSessionWallet;
|
||||||
|
mapping(address => address) private sessionWalletToPlayer;
|
||||||
|
mapping(address => address) private proposedSessionWallets;
|
||||||
|
|
||||||
// WETH
|
// WETH
|
||||||
IWETH public immutable weth = IWETH(WETH);
|
IWETH public immutable weth = IWETH(WETH);
|
||||||
// RGCVII token
|
// RGCVII token
|
||||||
@ -92,7 +97,10 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
modifier onlyActiveSession() {
|
modifier onlyActiveSession() {
|
||||||
require(players[msg.sender].has_active_session, "Session is not active, you need to buy into the game first");
|
address delegatedWallet = playerToSessionWallet[msg.sender];
|
||||||
|
require(
|
||||||
|
players[msg.sender].has_active_session ||
|
||||||
|
players[delegatedWallet].has_active_session, "Session is not active, you need to buy into the game first");
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +208,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
|
|
||||||
// Manual minting for itchy fingers
|
// Manual minting for itchy fingers
|
||||||
function raid() external onlyActiveSession {
|
function raid() external onlyActiveSession {
|
||||||
performRaid(msg.sender);
|
performRaid(_player());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper so we can use it when buying units too
|
// Helper so we can use it when buying units too
|
||||||
@ -240,9 +248,10 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
|
|
||||||
// Add a unit to your army
|
// Add a unit to your army
|
||||||
function addUnit(uint8 unit, uint16 n_units) external onlyActiveSession {
|
function addUnit(uint8 unit, uint16 n_units) external onlyActiveSession {
|
||||||
|
address player = _player();
|
||||||
require(unit <= 3, "Unknown unit");
|
require(unit <= 3, "Unknown unit");
|
||||||
|
|
||||||
Army storage army = armies[msg.sender];
|
Army storage army = armies[player];
|
||||||
uint16 currentLevel = 0;
|
uint16 currentLevel = 0;
|
||||||
if (unit == 0) {
|
if (unit == 0) {
|
||||||
// moloch_denier
|
// moloch_denier
|
||||||
@ -261,13 +270,13 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units);
|
uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units);
|
||||||
|
|
||||||
|
|
||||||
performRaid(msg.sender);
|
performRaid(player);
|
||||||
|
|
||||||
// TODO: Since we are first minting then burning the token, this could be simplified
|
// TODO: Since we are first minting then burning the token, this could be simplified
|
||||||
require(balanceOf(msg.sender) >= cost, "Not enough GELD to add this unit");
|
require(balanceOf(player) >= cost, "Not enough GELD to add this unit");
|
||||||
|
|
||||||
// then burn the cost of the new army
|
// then burn the cost of the new army
|
||||||
_burn(msg.sender, cost);
|
_burn(player, cost);
|
||||||
|
|
||||||
// Increase level
|
// Increase level
|
||||||
if (unit == 0) {
|
if (unit == 0) {
|
||||||
@ -289,7 +298,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
|
|
||||||
// Emit event
|
// Emit event
|
||||||
emit UnitAdded(
|
emit UnitAdded(
|
||||||
msg.sender,
|
player,
|
||||||
unit,
|
unit,
|
||||||
n_units,
|
n_units,
|
||||||
cost,
|
cost,
|
||||||
@ -303,15 +312,16 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
|
|
||||||
function battle_with_boss() external onlyActiveSession returns (bool[2] memory hasWonOrAscended) {
|
function battle_with_boss() external onlyActiveSession returns (bool[2] memory hasWonOrAscended) {
|
||||||
// first perform raid
|
// first perform raid
|
||||||
performRaid(msg.sender);
|
address player = _player();
|
||||||
Boss memory boss_to_attack = bosses[msg.sender];
|
performRaid(player);
|
||||||
|
Boss memory boss_to_attack = bosses[player];
|
||||||
// calculate how much the player will put into battle
|
// calculate how much the player will put into battle
|
||||||
uint256 geld_to_burn = balanceOf(msg.sender) >= RaidGeldUtils.getBossPower(boss_to_attack.level)
|
uint256 geld_to_burn = balanceOf(player) >= RaidGeldUtils.getBossPower(boss_to_attack.level)
|
||||||
? RaidGeldUtils.getBossPower(boss_to_attack.level)
|
? RaidGeldUtils.getBossPower(boss_to_attack.level)
|
||||||
: balanceOf(msg.sender);
|
: balanceOf(player);
|
||||||
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);
|
emit BossBattle(player, boss_to_attack.level, hasWonBattle);
|
||||||
lastBossResults[msg.sender] = LastBossResult({
|
lastBossResults[player] = LastBossResult({
|
||||||
battled_at: block.timestamp,
|
battled_at: block.timestamp,
|
||||||
level: boss_to_attack.level,
|
level: boss_to_attack.level,
|
||||||
variant: boss_to_attack.variants[boss_to_attack.level],
|
variant: boss_to_attack.variants[boss_to_attack.level],
|
||||||
@ -320,7 +330,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
});
|
});
|
||||||
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(player, geld_to_burn);
|
||||||
uint256 baseReward = (BUY_IN_DAO_TOKEN_AMOUNT - BUY_IN_DAO_TOKEN_AMOUNT * SACRIFICE_SHARE / MANTISSA);
|
uint256 baseReward = (BUY_IN_DAO_TOKEN_AMOUNT - BUY_IN_DAO_TOKEN_AMOUNT * SACRIFICE_SHARE / MANTISSA);
|
||||||
uint256 wholeReward = RaidGeldUtils.calculateBossReward(boss_to_attack.level, baseReward);
|
uint256 wholeReward = RaidGeldUtils.calculateBossReward(boss_to_attack.level, baseReward);
|
||||||
uint256 treasuryShare = wholeReward * SACRIFICE_SHARE / MANTISSA;
|
uint256 treasuryShare = wholeReward * SACRIFICE_SHARE / MANTISSA;
|
||||||
@ -328,29 +338,29 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
// send a share to dao treasury
|
// send a share to dao treasury
|
||||||
daoToken.transfer(DAO_TREASURY, treasuryShare);
|
daoToken.transfer(DAO_TREASURY, treasuryShare);
|
||||||
|
|
||||||
players[msg.sender].total_rewards += reward;
|
players[player].total_rewards += reward;
|
||||||
|
|
||||||
// send user its reward
|
// send user its reward
|
||||||
daoToken.transfer(msg.sender, reward);
|
daoToken.transfer(player, reward);
|
||||||
emit BossDefeated(msg.sender, boss_to_attack.level, reward);
|
emit BossDefeated(player, boss_to_attack.level, reward);
|
||||||
|
|
||||||
lastBossResults[msg.sender].reward = reward;
|
lastBossResults[player].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[player].prestige_level += 1;
|
||||||
emit PrestigeGained(msg.sender, players[msg.sender].prestige_level);
|
emit PrestigeGained(player, players[player].prestige_level);
|
||||||
player_dies(msg.sender);
|
player_dies(player);
|
||||||
lastBossResults[msg.sender].prestigeGained = true;
|
lastBossResults[player].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
|
||||||
bosses[msg.sender].level += 1;
|
bosses[player].level += 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Whoops u died, boss defeated you
|
// Whoops u died, boss defeated you
|
||||||
lastBossResults[msg.sender].reward = 0;
|
lastBossResults[player].reward = 0;
|
||||||
player_dies(msg.sender);
|
player_dies(player);
|
||||||
}
|
}
|
||||||
return [hasWonBattle, false /* hasnt gotten prestige level */ ];
|
return [hasWonBattle, false /* hasnt gotten prestige level */ ];
|
||||||
}
|
}
|
||||||
@ -428,6 +438,31 @@ contract RaidGeld is ERC20, Ownable, Constants {
|
|||||||
DAO = _dao;
|
DAO = _dao;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function proposeSessionWallet(address _wallet) payable external onlyPlayer {
|
||||||
|
require(!isRegistered(_wallet), "Wallet belongs to other player");
|
||||||
|
require(msg.value <= SESSION_WALLET_FUNDING_CAP, "Too high funding amoount");
|
||||||
|
require(sessionWalletToPlayer[_wallet] == address(0), "Wallet already in use");
|
||||||
|
playerToSessionWallet[msg.sender] = _wallet;
|
||||||
|
sessionWalletToPlayer[_wallet] = msg.sender;
|
||||||
|
payable(_wallet).call{value: msg.value}("");
|
||||||
|
}
|
||||||
|
function acceptSessionWallet(address _player) external {
|
||||||
|
require(proposedSessionWallets[_player] == msg.sender, "Not the proposed session wallet");
|
||||||
|
sessionWalletToPlayer[msg.sender] = _player;
|
||||||
|
playerToSessionWallet[_player] = msg.sender;
|
||||||
|
proposedSessionWallets[msg.sender] = address(0);
|
||||||
|
}
|
||||||
|
function fundSessionWallet() payable external {
|
||||||
|
require(sessionWalletToPlayer[msg.sender] != address(0), "No session wallet found");
|
||||||
|
require(msg.value <= SESSION_WALLET_FUNDING_CAP, "Too high funding amoount");
|
||||||
|
payable(sessionWalletToPlayer[msg.sender]).call{value: msg.value}("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _player() internal view returns (address) {
|
||||||
|
address delegatedWallet = sessionWalletToPlayer[msg.sender];
|
||||||
|
return delegatedWallet == address(0) ? msg.sender : delegatedWallet;
|
||||||
|
}
|
||||||
|
|
||||||
receive() external payable {
|
receive() external payable {
|
||||||
revert("No plain Ether accepted, use register() function to check in :)");
|
revert("No plain Ether accepted, use register() function to check in :)");
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user