From 86ad9c5e1a190ee4bdf76d20eaafd83bbd5b9b84 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Thu, 31 Oct 2024 03:37:31 +0100 Subject: [PATCH] Bosses in client --- app/public/background/frame_small.png | Bin 0 -> 2976 bytes app/src/components/Boss.tsx | 24 +++++ app/src/components/BossInfo.tsx | 92 ++++++++++++++++++ app/src/components/Counter.tsx | 4 +- app/src/components/Scene.tsx | 21 ++++- app/src/components/Tower.tsx | 16 +++- app/src/providers/PlayerProvider.tsx | 43 ++++++++- app/src/styles/Army.module.css | 2 +- app/src/styles/Background.module.css | 128 ++++++++++++++++++++++++-- app/src/styles/Info.module.css | 59 ++++++++++++ src/RaidGeld.sol | 2 +- src/RaidGeldUtils.sol | 2 +- test/RaidGeldUtils.t.sol | 10 ++ 13 files changed, 379 insertions(+), 24 deletions(-) create mode 100644 app/public/background/frame_small.png create mode 100644 app/src/components/Boss.tsx create mode 100644 app/src/components/BossInfo.tsx create mode 100644 app/src/styles/Info.module.css diff --git a/app/public/background/frame_small.png b/app/public/background/frame_small.png new file mode 100644 index 0000000000000000000000000000000000000000..0253edcb660e689599572151ce5fd878422d8769 GIT binary patch literal 2976 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 Wta-+?-~&ql0000 = { + 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
+} + +export default Boss diff --git a/app/src/components/BossInfo.tsx b/app/src/components/BossInfo.tsx new file mode 100644 index 0000000..eb36b23 --- /dev/null +++ b/app/src/components/BossInfo.tsx @@ -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 = { + 0: styles.boss0, + 1: styles.boss1, + 2: styles.boss2, + 3: styles.boss3, + 4: styles.boss4, + 5: styles.boss5, + 6: styles.boss6, +} + +const bossToName: Record = { + 0: "Gluttonous", + 1: "Slothful", + 2: "Lusty", + 3: "Wrathful", + 4: "Envious", + 5: "Prideful", + 6: "Greedy", +} + +const bossToReward: Record = { + 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 = { + 0: 0.99, + 1: 0.89, + 2: 0.8, + 3: 0.7, + 4: 0.62, + 5: 0.51, + 6: 0.40 +} + +const bossToBossPower: Record = { + 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
+

{bossToName[variant]} Moloch (lvl {boss ? boss.level + 1 : 0})

+

{formatUnits(bossToReward[boss?.level || 0], 18)} RGCVII reward

+

+ {chanceToDefeat.current * 100} % to slay{" "} + {chanceToDefeat.current == maxChance ? (MAXED) : (Max {maxChance * 100}%)} +

+
+} + +export default BossInfo diff --git a/app/src/components/Counter.tsx b/app/src/components/Counter.tsx index b1c0a32..843fa32 100644 --- a/app/src/components/Counter.tsx +++ b/app/src/components/Counter.tsx @@ -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( diff --git a/app/src/components/Scene.tsx b/app/src/components/Scene.tsx index c80fd96..997d97b 100644 --- a/app/src/components/Scene.tsx +++ b/app/src/components/Scene.tsx @@ -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
-
+ -
+
+
+
diff --git a/app/src/components/Tower.tsx b/app/src/components/Tower.tsx index 742cfde..cdd408c 100644 --- a/app/src/components/Tower.tsx +++ b/app/src/components/Tower.tsx @@ -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 = { + 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
diff --git a/app/src/providers/PlayerProvider.tsx b/app/src/providers/PlayerProvider.tsx index 0aec1a4..063bc35 100644 --- a/app/src/providers/PlayerProvider.tsx +++ b/app/src/providers/PlayerProvider.tsx @@ -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({ 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 ( {children} {txHash && } diff --git a/app/src/styles/Army.module.css b/app/src/styles/Army.module.css index 7552b78..e371096 100644 --- a/app/src/styles/Army.module.css +++ b/app/src/styles/Army.module.css @@ -199,7 +199,7 @@ bottom: 160px; width: 90px; height: 90px; - + user-select: none; .pixelQuote { min-width: 150px; color: black; diff --git a/app/src/styles/Background.module.css b/app/src/styles/Background.module.css index 83d7bb1..e46e014 100644 --- a/app/src/styles/Background.module.css +++ b/app/src/styles/Background.module.css @@ -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"); diff --git a/app/src/styles/Info.module.css b/app/src/styles/Info.module.css new file mode 100644 index 0000000..39ab2b2 --- /dev/null +++ b/app/src/styles/Info.module.css @@ -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); + } +} diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 426e45b..efb9d0d 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -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; diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index 0296489..6f51181 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -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 diff --git a/test/RaidGeldUtils.t.sol b/test/RaidGeldUtils.t.sol index bedb1f4..2859ab1 100644 --- a/test/RaidGeldUtils.t.sol +++ b/test/RaidGeldUtils.t.sol @@ -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); + } }