1
0
forked from mico/idle_moloch

Bosses in client

This commit is contained in:
mic0 2024-10-31 03:37:31 +01:00
parent 3504059ba8
commit 86ad9c5e1a
Signed by: mico
GPG Key ID: A3F8023524CF1C8D
13 changed files with 379 additions and 24 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,24 @@
import { BossLevel, usePlayer } from '../providers/PlayerProvider';
import styles from '../styles/Background.module.css';
export const bossLevelToClass: Record<BossLevel, string> = {
0: styles.boss0,
1: styles.boss1,
2: styles.boss2,
3: styles.boss3,
4: styles.boss4,
5: styles.boss5,
6: styles.boss6,
}
const Boss = () => {
const { battleWithBoss, boss } = usePlayer();
const variant = boss?.variants[boss.level] ?? 0;
return <div onClick={battleWithBoss} className={`
${styles.boss}
${bossLevelToClass[variant]}
${styles.background_asset}
`} />
}
export default Boss

View File

@ -0,0 +1,92 @@
import { formatUnits } from "viem"
import { BossLevel, usePlayer } from "../providers/PlayerProvider"
import styles from "../styles/Info.module.css"
import { useEffect, useReducer, useRef } from "react"
import { calculateBalance } from "./Counter"
export const bossLevelToClass: Record<BossLevel, string> = {
0: styles.boss0,
1: styles.boss1,
2: styles.boss2,
3: styles.boss3,
4: styles.boss4,
5: styles.boss5,
6: styles.boss6,
}
const bossToName: Record<BossLevel, string> = {
0: "Gluttonous",
1: "Slothful",
2: "Lusty",
3: "Wrathful",
4: "Envious",
5: "Prideful",
6: "Greedy",
}
const bossToReward: Record<BossLevel, bigint> = {
0: BigInt("200000000000000000"),
1: BigInt("28274420000000000000"),
2: BigInt("174191628800000000000"),
3: BigInt("513254698112000000000"),
4: BigInt("963499867554252800000"),
5: BigInt("1424610762718861153000"),
6: BigInt("1758160308403017784500"),
}
// for boss chances (percent) [99, 89, 80, 70, 62, 51, 40]
const bossToChance: Record<BossLevel, number> = {
0: 0.99,
1: 0.89,
2: 0.8,
3: 0.7,
4: 0.62,
5: 0.51,
6: 0.40
}
const bossToBossPower: Record<BossLevel, bigint> = {
0: BigInt("9000000"),
1: BigInt("90000000"),
2: BigInt("900000000"),
3: BigInt("9000000000"),
4: BigInt("90000000000"),
5: BigInt("900000000000"),
6: BigInt("9000000000000"),
}
const getBossChanceToDefeat = (bossLevel: BossLevel, geld_balance: bigint) => {
const bossPower = bossToBossPower[bossLevel]
const geldBurnt = geld_balance >= bossPower ? bossPower : geld_balance;
const relativePower = (geldBurnt * geldBurnt * BigInt("100")) / (bossPower * bossPower);
return bossToChance[bossLevel] * Number(relativePower) / 100
}
const BossInfo = () => {
const { boss, balance, player, army } = usePlayer();
const variant = boss?.variants[boss.level] || 0;
const maxChance = bossToChance[boss?.level || 0];
const chanceToDefeat = useRef(getBossChanceToDefeat(boss?.level || 0, balance ?? BigInt(0)));
const [, render] = useReducer(p => !p, false);
useEffect(() => {
const tickInterval = setInterval(() => {
chanceToDefeat.current = getBossChanceToDefeat(boss?.level ?? 0, calculateBalance(
balance ?? BigInt(0),
army?.profit_per_second ?? BigInt(0),
player?.last_raided_at ?? BigInt(0)
))
render();
}, 100);
return () => clearInterval(tickInterval)
}, [balance, army?.profit_per_second, player?.last_raided_at, boss?.level])
return <div className={styles.bossInfo}>
<p><strong className={bossLevelToClass[boss?.level || 0]}>{bossToName[variant]}</strong> Moloch <small>(lvl {boss ? boss.level + 1 : 0})</small></p>
<p><strong className={styles.reward}>{formatUnits(bossToReward[boss?.level || 0], 18)} RGCVII</strong> <small>reward</small></p>
<p>
<strong>{chanceToDefeat.current * 100} % to slay</strong>{" "}
{chanceToDefeat.current == maxChance ? <small className={styles.maxed}>(MAXED)</small> : <small>(Max {maxChance * 100}%)</small>}
</p>
</div>
}
export default BossInfo

View File

@ -55,8 +55,8 @@ export const toReadable = (rawValue: bigint) => {
const Counter = () => { const Counter = () => {
const { balance, army, player } = usePlayer(); const { balance, army, player } = usePlayer();
const [, render] = useReducer(p => !p, false); const [, render] = useReducer(p => !p, false);
const balanceCount = useRef(balance.toString() ?? "0") const balanceCount = useRef(balance ? balance.toString() : "0")
const availableBalance = useRef(balance.toString() ?? "0") const availableBalance = useRef(balance ? balance.toString() : "0")
useEffect(() => { useEffect(() => {
const tickInterval = setInterval(() => { const tickInterval = setInterval(() => {
balanceCount.current = toReadable(calculateBalance( balanceCount.current = toReadable(calculateBalance(

View File

@ -5,22 +5,37 @@ import Army from "./Army";
import MarchingBand from "./MarchingBand"; import MarchingBand from "./MarchingBand";
import MusicPlayer from "./MusicPlayer"; import MusicPlayer from "./MusicPlayer";
import { usePlayer } from "../providers/PlayerProvider"; import { usePlayer } from "../providers/PlayerProvider";
import Boss from "./Boss";
import BossInfo from "./BossInfo";
const bossToMountainsClass = {
0: styles.mountains0,
1: styles.mountains1,
2: styles.mountains2,
3: styles.mountains3,
4: styles.mountains4,
5: styles.mountains5,
6: styles.mountains6,
}
const Scene = () => { const Scene = () => {
const { isRegistered } = usePlayer(); const { isRegistered, boss } = usePlayer();
const handleMusicReady = useCallback((unmute: () => void) => { const handleMusicReady = useCallback((unmute: () => void) => {
if (isRegistered) { if (isRegistered) {
unmute(); unmute();
} }
}, [isRegistered]); }, [isRegistered]);
const variant = boss?.variants[boss.level] || 0;
return <div className={styles.frame}> return <div className={styles.frame}>
<div className={`${styles.air} ${styles.background_asset}`} /> <div className={`${styles.air} ${styles.background_asset}`} />
<div className={`${styles.clouds_small} ${styles.background_asset}`} />
<div className={`${styles.clouds_large} ${styles.background_asset}`} /> <div className={`${styles.clouds_large} ${styles.background_asset}`} />
<Boss />
<Tower /> <Tower />
<div className={`${styles.mountains} ${styles.background_asset}`} /> <div className={`${styles.clouds_small} ${styles.background_asset}`} />
<div className={`${styles.mountains} ${styles.background_asset} ${bossToMountainsClass[variant]}`} />
<div className={`${styles.village} ${styles.background_asset}`} /> <div className={`${styles.village} ${styles.background_asset}`} />
<BossInfo />
<MarchingBand /> <MarchingBand />
<div className={`${styles.bonfire} ${styles.background_asset}`} /> <div className={`${styles.bonfire} ${styles.background_asset}`} />
<Army /> <Army />

View File

@ -1,5 +1,5 @@
import { useEffect, useReducer, useRef } from 'react'; import { useEffect, useReducer, useRef } from 'react';
import { usePlayer } from '../providers/PlayerProvider'; import { BossLevel, usePlayer } from '../providers/PlayerProvider';
import styles from '../styles/Background.module.css'; import styles from '../styles/Background.module.css';
const onCooldown = (lastRaidedAt: bigint) => ( const onCooldown = (lastRaidedAt: bigint) => (
@ -10,10 +10,21 @@ const onCooldown = (lastRaidedAt: bigint) => (
const emptyFn = () => { } const emptyFn = () => { }
const bossLevelToClass: Record<BossLevel, string> = {
0: styles.tower0,
1: styles.tower1,
2: styles.tower2,
3: styles.tower3,
4: styles.tower4,
5: styles.tower5,
6: styles.tower6,
}
const Tower = () => { const Tower = () => {
const { raid, player } = usePlayer(); const { raid, player, boss } = usePlayer();
const isOnCooldown = useRef(false); const isOnCooldown = useRef(false);
const [, render] = useReducer(p => !p, false); const [, render] = useReducer(p => !p, false);
const variant = boss?.variants[boss.level] ?? 0;
useEffect(() => { useEffect(() => {
const checkCooldownInterval = setInterval(() => { const checkCooldownInterval = setInterval(() => {
@ -25,6 +36,7 @@ const Tower = () => {
return <div onClick={isOnCooldown.current ? emptyFn : raid} className={` return <div onClick={isOnCooldown.current ? emptyFn : raid} className={`
${styles.tower} ${styles.tower}
${bossLevelToClass[variant]}
${styles.background_asset} ${styles.background_asset}
${isOnCooldown.current ? styles.cooldown : ""} ${isOnCooldown.current ? styles.cooldown : ""}
`} /> `} />

View File

@ -9,11 +9,17 @@ 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 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,
n_runs: number,
prestige_level: number,
is_registered: boolean,
has_active_session: boolean,
} }
export interface Army { export interface Army {
anointed: { level: number } anointed: { level: number }
@ -22,14 +28,20 @@ export interface Army {
moloch_denier: { level: number } moloch_denier: { level: number }
profit_per_second: bigint profit_per_second: bigint
} }
export interface Boss {
level: BossLevel;
variants: [BossLevel, BossLevel, BossLevel, BossLevel, BossLevel, BossLevel, BossLevel]
}
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,
balance: bigint, balance: bigint,
register: (arg: "ETH" | "RGCVII") => void, register: (arg: "ETH" | "RGCVII") => void,
raid: () => void, raid: () => void,
battleWithBoss: () => void;
addUnit: (unit: UnitType) => void addUnit: (unit: UnitType) => void
} }
@ -37,9 +49,11 @@ const PlayerContext = createContext<PlayerContextType>({
isRegistered: false, isRegistered: false,
player: null, player: null,
army: null, army: null,
boss: null,
balance: BigInt(0), balance: BigInt(0),
register: () => { }, register: () => { },
raid: () => { }, raid: () => { },
battleWithBoss: () => { },
addUnit: () => { } addUnit: () => { }
}); });
@ -100,7 +114,18 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
} }
}); });
console.log(balance, player, army) const { data: boss } = useReadContract({
address: contractAddress,
abi,
functionName: 'getBoss',
args: [address],
query: {
enabled: isConnected,
refetchInterval: 15
}
});
console.log(balance, player, army, boss)
const register = useCallback((arg: "RGCVII" | "ETH") => { const register = useCallback((arg: "RGCVII" | "ETH") => {
if (arg === 'ETH') { if (arg === 'ETH') {
@ -108,7 +133,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
abi, abi,
address: contractAddress, address: contractAddress,
functionName: 'register_eth', functionName: 'register_eth',
value: parseEther("0.00005"), value: parseEther("0.0005"),
}, { }, {
onSuccess: (hash) => { onSuccess: (hash) => {
setHashAndCallback([hash, resetHashAndCallback]) setHashAndCallback([hash, resetHashAndCallback])
@ -119,7 +144,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
abi, abi,
address: daoTokenAddress, address: daoTokenAddress,
functionName: 'approve', functionName: 'approve',
args: [contractAddress, parseEther("50")], args: [contractAddress, parseEther("500")],
}, { }, {
onSuccess: (hash) => { onSuccess: (hash) => {
setHashAndCallback([ setHashAndCallback([
@ -156,15 +181,25 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
}) })
}, [writeContract]) }, [writeContract])
const battleWithBoss = useCallback(() => {
writeContract({
abi,
address: contractAddress,
functionName: 'battle_with_boss',
})
}, [writeContract])
return ( return (
<PlayerContext.Provider value={{ <PlayerContext.Provider 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,
balance: balance as bigint, balance: balance as bigint,
register, register,
raid, raid,
addUnit addUnit,
battleWithBoss
}}> }}>
{children} {children}
{txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />} {txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />}

View File

@ -199,7 +199,7 @@
bottom: 160px; bottom: 160px;
width: 90px; width: 90px;
height: 90px; height: 90px;
user-select: none;
.pixelQuote { .pixelQuote {
min-width: 150px; min-width: 150px;
color: black; color: black;

View File

@ -13,6 +13,7 @@
left: 22px; left: 22px;
right: 22px; right: 22px;
background-size: cover; background-size: cover;
pointer-events: none;
} }
.air { .air {
@ -44,14 +45,100 @@
scrollBackground 20s linear infinite, scrollBackground 20s linear infinite,
thunder 12s linear infinite; thunder 12s linear infinite;
} }
.tower {
background-image: url("/background/tower.png"); .boss {
width: 218px; background-image: url("/background/boss/0_gluttony.svg");
height: 240px; background-size: cover;
top: 150px; width: 270px;
height: 270px;
top: 130px;
right: 10px;
left: auto;
animation: thunder_hue_hard 12s linear infinite; animation: thunder_hue_hard 12s linear infinite;
transition: all 0.1s cubic-bezier(0.265, 1.4, 0.68, 1.65); transition: all 0.1s cubic-bezier(0.265, 1.4, 0.68, 1.65);
transform-origin: bottom center; transform-origin: bottom center;
pointer-events: all;
&:hover {
cursor: pointer;
transform: scale(1.05, 1.1);
transform-origin: bottom center;
&::after {
text-shadow: #0f0 1px 1px 10px;
animation: excited 0.5s infinite linear;
}
}
&:active {
transform: scale(1.1, 1.22);
}
&.boss0 {
background-image: url("/background/boss/0_gluttony.svg");
}
&.boss1 {
background-image: url("/background/boss/1_sloth.svg");
}
&.boss2 {
background-image: url("/background/boss/2_lust.svg");
}
&.boss3 {
background-image: url("/background/boss/3_wrath.svg");
}
&.boss4 {
background-image: url("/background/boss/4_envy.svg");
}
&.boss5 {
background-image: url("/background/boss/5_pride.svg");
}
&.boss6 {
background-image: url("/background/boss/6_greed.svg");
}
}
.boss::after {
position: absolute;
content: "BOSS\A(Risk & Earn RGCVII)";
text-align: center;
white-space: pre;
word-wrap: break-word;
left: 0;
width: 100%;
top: 40%;
margin-top: -10px;
}
.tower {
background-image: url("/background/tower/0_gluttony.svg");
background-size: cover;
width: 372px;
height: 372px;
top: 90px;
left: -10px;
animation: thunder_hue_hard 12s linear infinite;
transition: all 0.1s cubic-bezier(0.265, 1.4, 0.68, 1.65);
transform-origin: bottom center;
pointer-events: all;
&.tower0 {
background-image: url("/background/tower/0_gluttony.svg");
}
&.tower1 {
background-image: url("/background/tower/1_sloth.svg");
}
&.tower2 {
background-image: url("/background/tower/2_lust.svg");
}
&.tower3 {
background-image: url("/background/tower/3_wrath.svg");
}
&.tower4 {
background-image: url("/background/tower/4_envy.svg");
}
&.tower5 {
background-image: url("/background/tower/5_pride.svg");
}
&.tower6 {
background-image: url("/background/tower/6_greed.svg");
}
&:hover { &:hover {
cursor: pointer; cursor: pointer;
transform: scale(1.05, 1.1); transform: scale(1.05, 1.1);
@ -71,8 +158,6 @@
content: "RAID IN PROGRESS"; content: "RAID IN PROGRESS";
color: var(--hover-color); color: var(--hover-color);
top: calc(50% - 15px); top: calc(50% - 15px);
left: 0;
right: 0;
font-size: 0.9rem; font-size: 0.9rem;
text-align: center; text-align: center;
animation: excited 0.5s infinite linear; animation: excited 0.5s infinite linear;
@ -86,16 +171,39 @@
text-align: center; text-align: center;
white-space: pre; white-space: pre;
word-wrap: break-word; word-wrap: break-word;
left: 50px; left: 0;
top: 22px; width: 100%;
top: 40%;
margin-top: -10px;
} }
.mountains { .mountains {
background-image: url("/background/mountains.png"); background-image: url("/background/mountains/0_gluttony.svg");
height: 181px; height: 181px;
top: 285px; top: 285px;
animation: thunder_hue 12s linear infinite; animation: thunder_hue 12s linear infinite;
pointer-events: none; pointer-events: none;
&.mountains0 {
background-image: url("/background/mountains/0_gluttony.svg");
}
&.mountains1 {
background-image: url("/background/mountains/1_sloth.svg");
}
&.mountains2 {
background-image: url("/background/mountains/2_lust.svg");
}
&.mountains3 {
background-image: url("/background/mountains/3_wrath.svg");
}
&.mountains4 {
background-image: url("/background/mountains/4_envy.svg");
}
&.mountains5 {
background-image: url("/background/mountains/5_pride.svg");
}
&.mountains6 {
background-image: url("/background/mountains/6_greed.svg");
}
} }
.village { .village {
background-image: url("/background/village.png"); background-image: url("/background/village.png");

View File

@ -0,0 +1,59 @@
.bossInfo {
position: absolute;
top: 350px;
right: 28px;
background: var(--bg-color);
border-image: url("/background/frame_small.png") 11 fill / auto space;
padding: 18px 22px;
box-sizing: border-box;
& > p {
text-align: center;
margin: 0.2rem 0;
& > .reward {
color: var(--hover-color);
}
& > .maxed {
display: inline-block;
color: var(--accent-color);
opacity: 1;
animation: excited 0.5s infinite linear;
}
& > .boss0 {
color: #a44016;
}
& > .boss1 {
color: #99554d;
}
& > .boss2 {
color: #9b215e;
}
& > .boss3 {
color: #ebb638;
}
& > .boss4 {
color: #c6282e;
}
& > .boss5 {
color: #d06b53;
}
& > .boss6 {
color: #8f968f;
}
}
& small {
opacity: 0.7;
}
}
@keyframes excited {
0%,
100% {
transform: scale(1, 1) skew(0deg, 0deg);
}
10% {
transform: scale(1.2, 1.2) skew(0.5deg, -0.5deg);
}
90% {
transform: scale(1.02, 1.03) skew(0deg, -1deg);
}
}

View File

@ -10,7 +10,7 @@ import "../src/Constants.sol";
contract RaidGeld is ERC20, Ownable, Constants { contract RaidGeld is ERC20, Ownable, Constants {
uint256 public constant MANTISSA = 1e4; uint256 public constant MANTISSA = 1e4;
uint256 public constant BUY_IN_AMOUNT = 0.00005 ether; uint256 public constant BUY_IN_AMOUNT = 0.0005 ether;
uint256 public immutable BUY_IN_DAO_TOKEN_AMOUNT; uint256 public immutable BUY_IN_DAO_TOKEN_AMOUNT;
uint256 public constant INITIAL_GELD = 50 * MANTISSA; uint256 public constant INITIAL_GELD = 50 * MANTISSA;
mapping(address => Player) private players; mapping(address => Player) private players;

View File

@ -98,7 +98,7 @@ library RaidGeldUtils {
// TODO: This could as well just be pre-calculated // TODO: This could as well just be pre-calculated
uint256 cumulativeChance = getBossCumulativeChance(bossLevel); // 0 - 1e18 range uint256 cumulativeChance = getBossCumulativeChance(bossLevel); // 0 - 1e18 range
uint256 rewardMultiplier = ((2 * (1e18 - cumulativeChance)) ** 2) / 1e18; uint256 rewardMultiplier = ((2 * (1e18 - cumulativeChance)) ** 2) / 1e18;
return (baseReward * rewardMultiplier) / 1e18; return (baseReward * rewardMultiplier);
} }
// Calculates whether user survives the fight // Calculates whether user survives the fight

View File

@ -138,4 +138,14 @@ contract raid_geldTest is Test {
vm.assertTrue(successCount >= lowerProb && successCount <= upperProb, "Success rate not within expected range"); vm.assertTrue(successCount >= lowerProb && successCount <= upperProb, "Success rate not within expected range");
} }
} }
function test_4_print_boss_rewards() public {
uint256 total = 0;
for (uint8 i = 0; i < 7; i++) {
uint256 reward = RaidGeldUtils.calculateBossReward(i, 500);
console.log("Reward", i,reward);
total += reward;
}
console.log("Total", total);
}
} }