Bosses in client
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled

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 { balance, army, player } = usePlayer();
const [, render] = useReducer(p => !p, false);
const balanceCount = useRef(balance.toString() ?? "0")
const availableBalance = useRef(balance.toString() ?? "0")
const balanceCount = useRef(balance ? balance.toString() : "0")
const availableBalance = useRef(balance ? balance.toString() : "0")
useEffect(() => {
const tickInterval = setInterval(() => {
balanceCount.current = toReadable(calculateBalance(

View File

@ -5,22 +5,37 @@ import Army from "./Army";
import MarchingBand from "./MarchingBand";
import MusicPlayer from "./MusicPlayer";
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 { isRegistered } = usePlayer();
const { isRegistered, boss } = usePlayer();
const handleMusicReady = useCallback((unmute: () => void) => {
if (isRegistered) {
unmute();
}
}, [isRegistered]);
const variant = boss?.variants[boss.level] || 0;
return <div className={styles.frame}>
<div className={`${styles.air} ${styles.background_asset}`} />
<div className={`${styles.clouds_small} ${styles.background_asset}`} />
<div className={`${styles.clouds_large} ${styles.background_asset}`} />
<Boss />
<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}`} />
<BossInfo />
<MarchingBand />
<div className={`${styles.bonfire} ${styles.background_asset}`} />
<Army />

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@
left: 22px;
right: 22px;
background-size: cover;
pointer-events: none;
}
.air {
@ -44,14 +45,100 @@
scrollBackground 20s linear infinite,
thunder 12s linear infinite;
}
.tower {
background-image: url("/background/tower.png");
width: 218px;
height: 240px;
top: 150px;
.boss {
background-image: url("/background/boss/0_gluttony.svg");
background-size: cover;
width: 270px;
height: 270px;
top: 130px;
right: 10px;
left: auto;
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;
&: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 {
cursor: pointer;
transform: scale(1.05, 1.1);
@ -71,8 +158,6 @@
content: "RAID IN PROGRESS";
color: var(--hover-color);
top: calc(50% - 15px);
left: 0;
right: 0;
font-size: 0.9rem;
text-align: center;
animation: excited 0.5s infinite linear;
@ -86,16 +171,39 @@
text-align: center;
white-space: pre;
word-wrap: break-word;
left: 50px;
top: 22px;
left: 0;
width: 100%;
top: 40%;
margin-top: -10px;
}
.mountains {
background-image: url("/background/mountains.png");
background-image: url("/background/mountains/0_gluttony.svg");
height: 181px;
top: 285px;
animation: thunder_hue 12s linear infinite;
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 {
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 {
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 constant INITIAL_GELD = 50 * MANTISSA;
mapping(address => Player) private players;

View File

@ -98,7 +98,7 @@ library RaidGeldUtils {
// TODO: This could as well just be pre-calculated
uint256 cumulativeChance = getBossCumulativeChance(bossLevel); // 0 - 1e18 range
uint256 rewardMultiplier = ((2 * (1e18 - cumulativeChance)) ** 2) / 1e18;
return (baseReward * rewardMultiplier) / 1e18;
return (baseReward * rewardMultiplier);
}
// 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");
}
}
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);
}
}