Compare commits

..

No commits in common. "b5dbe91e8a72a7a23779e0ea04e14d9b495e8f53" and "a5fd3d9e214da873f65c11c87a796139840205f7" have entirely different histories.

8 changed files with 171 additions and 348 deletions

15
app/package-lock.json generated
View File

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

View File

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

View File

@ -4,76 +4,38 @@ 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> {rewardText}
{rewardText} <div>
<div> <button onClick={() => setIsOpen(false)}>Onward!</button>
<button onClick={() => setIsOpen(false)}>Onward!</button>
</div>
</div> </div>
); </div>
}; }
export default BossOutcomeModal; export default BossOutcomeModal

View File

@ -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}>
@ -55,19 +41,11 @@ function MyApp({ Component, pageProps }: AppProps) {
font-family: ${font.style.fontFamily}; font-family: ${font.style.fontFamily};
} }
.pixelFont { .pixelFont {
font-family: ${fontPixel.style.fontFamily}; font-family: ${fontPixel.style.fontFamily};
} }
`}</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>

View File

@ -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;
} }
@ -80,199 +63,167 @@ const PlayerContext = createContext<PlayerContextType>({
boss: null, boss: null,
lastBossResult: null, lastBossResult: null,
balance: BigInt(0), balance: BigInt(0),
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: 10000,
}, }
}); });
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: 10000,
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: 10000
}, }
}); });
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: 10000
}, }
}); });
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: 10000
}, }
}); });
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: 10000
}, }
}); });
const register = useCallback( const register = useCallback((arg: "RGCVII" | "ETH") => {
(arg: "RGCVII" | "ETH") => { if (arg === 'ETH') {
if (arg === "ETH") { writeContract({
writeContract( abi,
{ address: contractAddress,
abi, functionName: 'register_eth',
address: contractAddress, value: parseEther("0.00045"),
functionName: "register_eth", }, {
value: parseEther("0.00045"), onSuccess: (hash) => {
}, setHashAndCallback([hash, resetHashAndCallback])
{ },
onSuccess: (hash) => { onError: () => resetHashAndCallback()
setHashAndCallback([hash, resetHashAndCallback]); })
}, } else if (arg === "RGCVII") {
onError: () => resetHashAndCallback(), writeContract({
} abi,
); address: daoTokenAddress,
} else if (arg === "RGCVII") { functionName: 'approve',
writeContract( args: [contractAddress, parseEther("400")],
{ }, {
abi, onSuccess: (hash) => {
address: daoTokenAddress, setHashAndCallback([
functionName: "approve", hash,
args: [contractAddress, parseEther("400")], () => writeContract({
}, abi,
{ address: contractAddress,
onSuccess: (hash) => { functionName: 'register_dao',
setHashAndCallback([ }, {
hash, onSuccess: (hash) => {
() => setHashAndCallback([hash, resetHashAndCallback])
writeContract( },
{ onError: () => resetHashAndCallback()
abi, })
address: contractAddress, ])
functionName: "register_dao", },
}, onError: () => resetHashAndCallback()
{ });
onSuccess: (hash) => { }
setHashAndCallback([hash, resetHashAndCallback]); }, [writeContract, resetHashAndCallback])
},
onError: () => resetHashAndCallback(),
}
),
]);
},
onError: () => resetHashAndCallback(),
}
);
}
},
[writeContract, resetHashAndCallback]
);
const raid = useCallback(() => { const raid = useCallback(() => {
writeContract( writeContract({
{ abi,
abi, address: contractAddress,
address: contractAddress, functionName: 'raid',
functionName: "raid", }, {
onSuccess: (hash) => {
setHashAndCallback([hash, resetHashAndCallback])
}, },
{ onError: () => resetHashAndCallback()
onSuccess: (hash) => { })
setHashAndCallback([hash, resetHashAndCallback]); }, [writeContract, resetHashAndCallback])
},
onError: () => resetHashAndCallback(),
}
);
}, [writeContract, resetHashAndCallback]);
const addUnit = useCallback( const addUnit = useCallback((unit: UnitType) => {
(unit: UnitType) => { writeContract({
writeContract( abi,
{ address: contractAddress,
abi, functionName: 'addUnit',
address: contractAddress, args: [unit, 1]
functionName: "addUnit", })
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) => {
setHashAndCallback([hash, () => resetHashAndCallback()])
}, },
{ onError: () => resetHashAndCallback()
onSuccess: (hash) => { })
setHashAndCallback([hash, () => resetHashAndCallback()]); }, [writeContract, resetHashAndCallback])
},
onError: () => resetHashAndCallback(),
}
);
}, [writeContract, resetHashAndCallback]);
useEffect(() => { useEffect(() => {
if (lastBossResult != null) { if (lastBossResult != null) {
@ -282,40 +233,33 @@ 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, boss: boss as Boss,
boss: boss as Boss, balance: balance as bigint,
balance: balance as bigint, lastBossResult: lastBossResult as LastBossResult,
lastBossResult: lastBossResult as LastBossResult, 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;

View File

@ -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();
};