From 1afd975e0fba3a515718bd5e76735ac708c3287f Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Wed, 30 Oct 2024 00:28:48 +0100 Subject: [PATCH 01/19] WIP adding bosses --- src/RaidGeld.sol | 132 ++++++++++++++++++++++++---------------- src/RaidGeldStructs.sol | 9 +++ src/RaidGeldUtils.sol | 50 ++++++++++++++- test/RaidGeld.t.sol | 50 +++------------ 4 files changed, 148 insertions(+), 93 deletions(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 83ef9e4..dcbef9b 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -5,7 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import {RaidGeldUtils} from "../src/RaidGeldUtils.sol"; -import {Army, Player, Raider} from "../src/RaidGeldStructs.sol"; +import {Army, Player, Raider, Boss} from "../src/RaidGeldStructs.sol"; import "../src/Constants.sol"; contract RaidGeld is ERC20, Ownable, Constants { @@ -15,6 +15,7 @@ contract RaidGeld is ERC20, Ownable, Constants { uint256 public constant INITIAL_GELD = 500 * MANTISSA; mapping(address => Player) private players; mapping(address => Army) private armies; + mapping(address => Boss) private bosses; // WETH IWETH public immutable weth = IWETH(WETH); @@ -25,12 +26,9 @@ contract RaidGeld is ERC20, Ownable, Constants { // Uniswap ISwapRouter02 private constant router = ISwapRouter02(SWAP_ROUTER); // Events + event PlayerRegistered(address indexed player, uint256 initialGeld); - event RaidPerformed( - address indexed player, - uint256 totalMinted, - uint256 geldBalance - ); + event RaidPerformed(address indexed player, uint256 totalMinted, uint256 geldBalance); event UnitAdded( address indexed player, uint8 unitType, @@ -49,25 +47,54 @@ contract RaidGeld is ERC20, Ownable, Constants { _; } - modifier newPlayer() { - require(players[msg.sender].created_at == 0, "Whoops, player already exists :)"); + modifier onlyActiveSession() { + require(players[msg.sender].active_session, "Session is not active, you need to buy into the game first"); + _; + } + + modifier newPlay() { + Player memory player = players[msg.sender]; + bool notRegistered = player.created_at == 0; + bool returningPlayer = player.has_registered && !player.has_active_session; + require(notRegistered || returningPlayer, "Active session already in progress"); _; } constructor(address _daoToken, address _pool) ERC20("Raid Geld", "GELD") Ownable(msg.sender) { daoToken = ERC20(_daoToken); pool = _pool; - BUY_IN_DAO_TOKEN_AMOUNT = 50 * 10 ** daoToken.decimals(); + BUY_IN_DAO_TOKEN_AMOUNT = 500 * 10 ** daoToken.decimals(); } function init_player(address player) private { + bool existing_player = players[player].is_registered; + // Mint some starting tokens to the player _mint(player, INITIAL_GELD); + // Reset or set player + reset_player(player); + + if (existing_player) { + // TODO: Emit new run + } else { + // Emit event + emit PlayerRegistered(msg.sender, INITIAL_GELD); + } + } + + function reset_player(address addr) private { + Player memory player = players[addr]; + uint32 current_n_runs = player.n_runs + 1; + uint256 current_total_rewards = player.total_rewards; + bool has_registered = player.is_registered; // Set initial states players[msg.sender] = Player({ total_minted: INITIAL_GELD, created_at: block.timestamp, - last_raided_at: block.timestamp + last_raided_at: block.timestamp, + n_runs: current_n_runs, + total_rewards: current_total_rewards, + active_session: false }); armies[msg.sender] = Army({ moloch_denier: Raider({level: 0}), @@ -76,13 +103,11 @@ contract RaidGeld is ERC20, Ownable, Constants { champion: Raider({level: 0}), profit_per_second: 0 }); - - // Emit event - emit PlayerRegistered(msg.sender, INITIAL_GELD); + bosses[msg.sender] = Boss({level: 0, variant: 0}); } // New player want to register with ETH - function register_eth() external payable newPlayer { + function register_eth() external payable newPlay { require(msg.value == BUY_IN_AMOUNT, "Incorrect buy in amount"); weth.deposit{value: BUY_IN_AMOUNT}(); weth.approve(address(router), BUY_IN_AMOUNT); @@ -100,7 +125,7 @@ contract RaidGeld is ERC20, Ownable, Constants { } // New player wants to register with dao - function register_dao() external payable newPlayer { + function register_dao() external payable newPlay { //@notice this is not safe for arbitrary tokens, which may not follow the interface eg. USDT //@notice but should be fine for the DAO token require( @@ -123,25 +148,18 @@ contract RaidGeld is ERC20, Ownable, Constants { } // Manual minting for itchy fingers - function raid() external onlyPlayer { - uint256 totalMinted = performRaid(msg.sender); - - // Emit event - emit RaidPerformed(msg.sender, totalMinted, balanceOf(msg.sender)); + function raid() external onlyActiveSession { + performRaid(); } // Helper so we can use it when buying units too - function performRaid(address player) private returns (uint256) { + function performRaid(address player) private { uint256 time_past = block.timestamp - players[player].last_raided_at; uint256 new_geld = armies[player].profit_per_second * time_past; - - // TODO: Pink noise, make it so sometimes its better than expected - _mint(player, new_geld); players[player].last_raided_at = block.timestamp; players[player].total_minted += new_geld; - - return players[player].total_minted; + emit RaidPerformed(player, players[player].total_minted, balanceOf(player)); } // Function to get Player struct @@ -160,7 +178,7 @@ contract RaidGeld is ERC20, Ownable, Constants { } // Add a unit to your army - function addUnit(uint8 unit, uint16 n_units) external onlyPlayer { + function addUnit(uint8 unit, uint16 n_units) external onlyActiveSession { require(unit <= 3, "Unknown unit"); Army storage army = armies[msg.sender]; @@ -179,24 +197,15 @@ contract RaidGeld is ERC20, Ownable, Constants { currentLevel = army.champion.level; } - uint256 cost = RaidGeldUtils.calculateUnitPrice( - unit, - currentLevel, - n_units - ); - // First trigger a raid so player receives what he is due at to this moment + uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units); - uint256 time_past = block.timestamp - - players[msg.sender].last_raided_at; + // First trigger a raid so player receives what he is due at to this moment + uint256 time_past = block.timestamp - players[msg.sender].last_raided_at; uint256 new_geld = armies[msg.sender].profit_per_second * time_past; - require( - balanceOf(msg.sender) + new_geld >= cost, - "Not enough GELD to add this unit" - ); + require(balanceOf(msg.sender) + new_geld >= cost, "Not enough GELD to add this unit"); uint256 totalMinted = performRaid(msg.sender); // Emit event - emit RaidPerformed(msg.sender, totalMinted, balanceOf(msg.sender)); // TODO: Since we are first minting then burning the token, this could be simplified // by first calculating the difference and then minting / burning in just one operation @@ -236,10 +245,37 @@ contract RaidGeld is ERC20, Ownable, Constants { ); } - receive() external payable { - revert( - "No plain Ether accepted, use register() function to check in :)" + function battle_with_boss() external onlyActiveSession { + // first perform raid + performRaid(); + Boss memory boss_to_attack = bosses[msg.sender]; + // calculate how much the player will put into battle + uint256 geld_to_burn = balanceOf(msg.sender) >= RaidGeldUtils.BOSS_POWERS[boss_to_attack.level] + ? RaidGeldUtils.BOSS_POWERS[boss_to_attack.level] + : balanceOf(msg.sender); + bool hasWonBattle = RaidGeldUtils.calculateBossFight( + boss_to_attack.level, geld_to_burn, block.timestamp, block.difficulty ); + if (hasWonBattle) { + // Burn geld, get some sweet DAO Token and continue + _burn(msg.sender, geld_to_burn); + uint256 reward = RaidGeldUtils.calculateBossReward(boss_to_attack.level); + players[msg.sender].total_rewards += reward; + daoToken.transferFrom(address(this), msg.sender, reward); + } else { + // Whoops u die + player_dies(msg.sender); + } + } + + function player_dies(address player) private { + reset_player(); + players[player].has_active_session = false; + _burn(msg.sender, balanceOf(player)); + } + + receive() external payable { + revert("No plain Ether accepted, use register() function to check in :)"); } // Revert any non-function-call Ether transfers or calls to non-existent functions @@ -259,10 +295,7 @@ interface ISwapRouter02 { uint160 sqrtPriceLimitX96; } - function exactInputSingle(ExactInputSingleParams calldata params) - external - payable - returns (uint256 amountOut); + function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); struct ExactOutputSingleParams { address tokenIn; @@ -274,10 +307,7 @@ interface ISwapRouter02 { uint160 sqrtPriceLimitX96; } - function exactOutputSingle(ExactOutputSingleParams calldata params) - external - payable - returns (uint256 amountIn); + function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn); } interface IWETH is IERC20 { diff --git a/src/RaidGeldStructs.sol b/src/RaidGeldStructs.sol index 9846c09..5d70a96 100644 --- a/src/RaidGeldStructs.sol +++ b/src/RaidGeldStructs.sol @@ -17,4 +17,13 @@ struct Player { uint256 total_minted; uint256 created_at; uint256 last_raided_at; + uint256 total_rewards; + uint32 n_runs; + bool is_registered; + bool has_active_session; +} + +struct Boss { + uint8 variant; + uint8 level; } diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index 42d4c98..6f76b78 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -19,6 +19,26 @@ library RaidGeldUtils { uint256 constant ANOINTED_BASE_COST = 30096000; uint256 constant CHAMPION_BASE_COST = 255816000; + // Boss lvels + uint256[7] constant BOSS_POWERS = [ + 9000000, // 900e4 + 90000000, // 900e5 + 900000000, // 900e6 + 9000000000, // 900e7 + 90000000000, // 900e8 + 900000000000, // 900e9 + 9000000000000 // 900e10 + ]; + + // First boss is almost guaranteed, others arent + uint256[7] constant BOSS_CHANCES = [99, 89, 80, 70, 62, 51, 40]; + + // These are cumulative chances from above with precision 18 + uint256[7] constant CUMULATIVE_BOSS_CHANCES = + [99e16, 8811e14, 7048e14, 493416e12, 30591792e10, 1560181392e8, 6240725568e7]; + + uint256 constant RISK_BETA = 15e17; + function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) { require(unit <= 3, "No matching unit found"); uint256 rollingPriceCalculation = MOLOCH_DENIER_BASE_COST; @@ -43,7 +63,6 @@ library RaidGeldUtils { function calculateProfitsPerSecond(Army memory army) internal pure returns (uint256) { // Each next unit scales progressivelly better - uint256 moloch_denier_profit = army.moloch_denier.level * MOLOCH_DENIER_PROFIT; uint256 apprentice_profit = army.apprentice.level * APPRENTICE_PROFIT; uint256 anointed_profit = army.anointed.level * ANOINTED_PROFIT; @@ -51,4 +70,33 @@ library RaidGeldUtils { return moloch_denier_profit + apprentice_profit + anointed_profit + champion_profit; } + + // Returns how much Dao Token player is due for winning + function calculateBossReward(uint256 bossLevel) public view returns (uint256) { + return BOSS_POWERS[bossLevel] * (CUMULATIVE_BOSS_CHANCES[bossLevel] * 10 / 5) ** 2; + } + + // Calculates whether user survives the fight + function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 timestamp, uint256 difficulty) + internal + pure + returns (uint256) + { + uint256 bossPower = BOSS_POWERS[bossLevel]; + require(geldBurnt <= bossPower, "Cant try to defeat boss with more than what boss power is"); + uint256 random_n = random(timestamp, difficulty, 1, 100); + // Relative power as in, you can only put in 800 geld to defeat 900 geld boss, + // but you will get exponentially worse chances + uint256 relativePower = ((geldBurnt ** 2) * 100) / bossPower ** 2; + uint256 roll = (random_n * relativePower * PRECISION) / (100 * PRECISION); + return roll >= bossPower; + } + + // TODO: Implement actual randomness + function random(uint256 timestamp, uint256 difficulty, uint256 min, uint256 max) internal pure returns (uint256) { + // returns 0 - 100 + require(max >= min, "Max must be greater than or equal to min"); + uint256 range = max - min + 1; + return min + (uint256(keccak256(abi.encodePacked(timestamp, difficulty))) % range); + } } diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index 300acc6..b58297c 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -17,11 +17,7 @@ contract raid_geldTest is Test, Constants { event Approval(address indexed owner, address indexed spender, uint256 value); event PlayerRegistered(address indexed player, uint256 initialGeld); - event RaidPerformed( - address indexed player, - uint256 totalMinted, - uint256 geldBalance - ); + event RaidPerformed(address indexed player, uint256 totalMinted, uint256 geldBalance); event UnitAdded( address indexed player, uint8 unitType, @@ -61,7 +57,7 @@ contract raid_geldTest is Test, Constants { function test_00_no_fallback() public { vm.expectRevert(); // Send Ether with some data to trigger fallback - (bool success, ) = address(raid_geld).call{value: 0.1 ether}("0x1234"); + (bool success,) = address(raid_geld).call{value: 0.1 ether}("0x1234"); } function test_01_no_receive() public { @@ -113,7 +109,7 @@ contract raid_geldTest is Test, Constants { uint256 initialBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); // Making sure event is emitted when player is registered - // doesnt test player emitted event because other events get emitted before it + // doesnt test player emitted event because other events get emitted before it registerPlayerWithDaoToken(); // Check that initial raid_geld is received by the player @@ -136,7 +132,7 @@ contract raid_geldTest is Test, Constants { assertEq(army.apprentice.level, 0); assertEq(army.anointed.level, 0); assertEq(army.champion.level, 0); - } + } function test_03_dao_token_can_be_withdrawn() public { uint256 initialBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); @@ -144,7 +140,7 @@ contract raid_geldTest is Test, Constants { // Switch to Player 1 and register it vm.startPrank(player1); - // doesnt test player emitted event because other events get emitted before it + // doesnt test player emitted event because other events get emitted before it registerPlayerWithDaoToken(); // Switch back to owner and withdraw funds @@ -211,17 +207,7 @@ contract raid_geldTest is Test, Constants { // Making sure event is emitted when player adds a unit vm.expectEmit(address(raid_geld)); - emit UnitAdded( - address(player1), - 0, - 1, - cost, - playerBalance - cost, - 1, - 0, - 0, - 0 - ); + emit UnitAdded(address(player1), 0, 1, cost, playerBalance - cost, 1, 0, 0, 0); // Add 1 unit raid_geld.addUnit(0, 1); @@ -257,17 +243,7 @@ contract raid_geldTest is Test, Constants { // Making sure event is emitted when player adds a unit vm.expectEmit(address(raid_geld)); - emit UnitAdded( - address(player1), - 0, - 1, - cost, - playerBalance - cost, - 1, - 0, - 0, - 0 - ); + emit UnitAdded(address(player1), 0, 1, cost, playerBalance - cost, 1, 0, 0, 0); // bought 1 moloch_denier raid_geld.addUnit(0, 1); @@ -282,11 +258,7 @@ contract raid_geldTest is Test, Constants { // Making sure event is emitted when player performs a raid vm.expectEmit(address(raid_geld)); - emit RaidPerformed( - address(player1), - player.total_minted + amountMinted, - balance + amountMinted - ); + emit RaidPerformed(address(player1), player.total_minted + amountMinted, balance + amountMinted); // Trigger raid funds minting raid_geld.raid(); @@ -302,11 +274,7 @@ contract raid_geldTest is Test, Constants { amountMinted = army.profit_per_second * 15; - emit RaidPerformed( - address(player1), - player.total_minted + amountMinted, - balance + amountMinted - ); + emit RaidPerformed(address(player1), player.total_minted + amountMinted, balance + amountMinted); raid_geld.raid(); From ecbadf3809ca5f7ffb7ab4ad512b3bfcd803a38e Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Wed, 30 Oct 2024 11:58:17 +0100 Subject: [PATCH 02/19] Boss mechanics compile --- src/RaidGeld.sol | 25 ++++++++--------- src/RaidGeldUtils.sol | 59 ++++++++++++++++++++++------------------ test/RaidGeldUtils.t.sol | 2 +- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index e940ef5..2dcce8b 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -48,14 +48,14 @@ contract RaidGeld is ERC20, Ownable, Constants { } modifier onlyActiveSession() { - require(players[msg.sender].active_session, "Session is not active, you need to buy into the game first"); + require(players[msg.sender].has_active_session, "Session is not active, you need to buy into the game first"); _; } modifier newPlay() { Player memory player = players[msg.sender]; bool notRegistered = player.created_at == 0; - bool returningPlayer = player.has_registered && !player.has_active_session; + bool returningPlayer = player.is_registered && !player.has_active_session; require(notRegistered || returningPlayer, "Active session already in progress"); _; } @@ -86,7 +86,7 @@ contract RaidGeld is ERC20, Ownable, Constants { Player memory player = players[addr]; uint32 current_n_runs = player.n_runs + 1; uint256 current_total_rewards = player.total_rewards; - bool has_registered = player.is_registered; + bool current_is_registered = player.is_registered; // Set initial states players[msg.sender] = Player({ total_minted: INITIAL_GELD, @@ -94,7 +94,8 @@ contract RaidGeld is ERC20, Ownable, Constants { last_raided_at: block.timestamp, n_runs: current_n_runs, total_rewards: current_total_rewards, - active_session: false + has_active_session: false, + is_registered: current_is_registered }); armies[msg.sender] = Army({ moloch_denier: Raider({level: 0}), @@ -148,7 +149,7 @@ contract RaidGeld is ERC20, Ownable, Constants { // Manual minting for itchy fingers function raid() external onlyActiveSession { - performRaid(); + performRaid(msg.sender); } // Helper so we can use it when buying units too @@ -202,9 +203,7 @@ contract RaidGeld is ERC20, Ownable, Constants { uint256 time_past = block.timestamp - players[msg.sender].last_raided_at; uint256 new_geld = armies[msg.sender].profit_per_second * time_past; require(balanceOf(msg.sender) + new_geld >= cost, "Not enough GELD to add this unit"); - uint256 totalMinted = performRaid(msg.sender); - - // Emit event + performRaid(msg.sender); // TODO: Since we are first minting then burning the token, this could be simplified // by first calculating the difference and then minting / burning in just one operation @@ -246,14 +245,14 @@ contract RaidGeld is ERC20, Ownable, Constants { function battle_with_boss() external onlyActiveSession { // first perform raid - performRaid(); + performRaid(msg.sender); Boss memory boss_to_attack = bosses[msg.sender]; // calculate how much the player will put into battle - uint256 geld_to_burn = balanceOf(msg.sender) >= RaidGeldUtils.BOSS_POWERS[boss_to_attack.level] - ? RaidGeldUtils.BOSS_POWERS[boss_to_attack.level] + uint256 geld_to_burn = balanceOf(msg.sender) >= RaidGeldUtils.getBossPower(boss_to_attack.level) + ? RaidGeldUtils.getBossPower(boss_to_attack.level) : balanceOf(msg.sender); bool hasWonBattle = RaidGeldUtils.calculateBossFight( - boss_to_attack.level, geld_to_burn, block.timestamp, block.difficulty + boss_to_attack.level, geld_to_burn, block.prevrandao ); if (hasWonBattle) { // Burn geld, get some sweet DAO Token and continue @@ -268,7 +267,7 @@ contract RaidGeld is ERC20, Ownable, Constants { } function player_dies(address player) private { - reset_player(); + reset_player(player); players[player].has_active_session = false; _burn(msg.sender, balanceOf(player)); } diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index 6f76b78..f793a44 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -19,26 +19,32 @@ library RaidGeldUtils { uint256 constant ANOINTED_BASE_COST = 30096000; uint256 constant CHAMPION_BASE_COST = 255816000; - // Boss lvels - uint256[7] constant BOSS_POWERS = [ - 9000000, // 900e4 - 90000000, // 900e5 - 900000000, // 900e6 - 9000000000, // 900e7 - 90000000000, // 900e8 - 900000000000, // 900e9 - 9000000000000 // 900e10 - ]; - - // First boss is almost guaranteed, others arent - uint256[7] constant BOSS_CHANCES = [99, 89, 80, 70, 62, 51, 40]; - - // These are cumulative chances from above with precision 18 - uint256[7] constant CUMULATIVE_BOSS_CHANCES = - [99e16, 8811e14, 7048e14, 493416e12, 30591792e10, 1560181392e8, 6240725568e7]; - uint256 constant RISK_BETA = 15e17; + function getBossPower(uint8 level) internal pure returns (uint256 power) { + require(level <= 7, "Bosses only go to level 7"); + if (level == 0) return 9000000; // 900e4 + else if (level == 1) return 90000000; // 900e5 + else if (level == 2) return 900000000; // 900e6 + else if (level == 3) return 9000000000; // 900e7 + else if (level == 4) return 90000000000; // 900e8 + else if (level == 5) return 900000000000; // 900e9 + else if (level == 6) return 9000000000000; // 900e10 + } + + function getBossCumulativeChance(uint8 level) internal pure returns (uint256 chance) { + // for boss chances (percent) [99, 89, 80, 70, 62, 51, 40] + // where cumulative chance is in the form of 18 precision related to 1e18 + require(level <= 7, "Bosses only go to level 7"); + if (level == 0) return 99e16; + else if (level == 1) return 8811e14; + else if (level == 2) return 7048e14; + else if (level == 3) return 493416e12; + else if (level == 4) return 30591792e10; + else if (level == 5) return 1560181392e8; + else if (level == 6) return 6240725568e7; + } + function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) { require(unit <= 3, "No matching unit found"); uint256 rollingPriceCalculation = MOLOCH_DENIER_BASE_COST; @@ -67,24 +73,23 @@ library RaidGeldUtils { uint256 apprentice_profit = army.apprentice.level * APPRENTICE_PROFIT; uint256 anointed_profit = army.anointed.level * ANOINTED_PROFIT; uint256 champion_profit = army.champion.level * CHAMPION_PROFIT; - return moloch_denier_profit + apprentice_profit + anointed_profit + champion_profit; } // Returns how much Dao Token player is due for winning - function calculateBossReward(uint256 bossLevel) public view returns (uint256) { - return BOSS_POWERS[bossLevel] * (CUMULATIVE_BOSS_CHANCES[bossLevel] * 10 / 5) ** 2; + function calculateBossReward(uint8 bossLevel) internal pure returns (uint256) { + return getBossPower(bossLevel) * (getBossCumulativeChance(bossLevel) * 10 / 5) ** 2; } // Calculates whether user survives the fight - function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 timestamp, uint256 difficulty) + function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 prevrandao) internal pure - returns (uint256) + returns (bool) { - uint256 bossPower = BOSS_POWERS[bossLevel]; + uint256 bossPower = getBossPower(bossLevel); require(geldBurnt <= bossPower, "Cant try to defeat boss with more than what boss power is"); - uint256 random_n = random(timestamp, difficulty, 1, 100); + uint256 random_n = random(prevrandao, 1, 100); // Relative power as in, you can only put in 800 geld to defeat 900 geld boss, // but you will get exponentially worse chances uint256 relativePower = ((geldBurnt ** 2) * 100) / bossPower ** 2; @@ -93,10 +98,10 @@ library RaidGeldUtils { } // TODO: Implement actual randomness - function random(uint256 timestamp, uint256 difficulty, uint256 min, uint256 max) internal pure returns (uint256) { + function random(uint256 prevrandao, uint256 min, uint256 max) internal pure returns (uint256) { // returns 0 - 100 require(max >= min, "Max must be greater than or equal to min"); uint256 range = max - min + 1; - return min + (uint256(keccak256(abi.encodePacked(timestamp, difficulty))) % range); + return min + (uint256(keccak256(abi.encodePacked(prevrandao))) % range); } } diff --git a/test/RaidGeldUtils.t.sol b/test/RaidGeldUtils.t.sol index 5658e60..fbda317 100644 --- a/test/RaidGeldUtils.t.sol +++ b/test/RaidGeldUtils.t.sol @@ -28,7 +28,7 @@ contract raid_geldTest is Test { assertGt(price, basePriceChamp * 12); } - function test_1_profits_per_second(uint16 _dLvl, uint16 _apLvl, uint16 _anLvl, uint16 _cLvl) public { + function test_1_profits_per_second(uint16 _dLvl, uint16 _apLvl, uint16 _anLvl, uint16 _cLvl) public pure { Army memory army = Army({ moloch_denier: Raider({level: 1}), apprentice: Raider({level: 0}), From 2e6bee24bf60147576d8f1f87fcba70e2b6476dc Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Wed, 30 Oct 2024 12:34:58 +0100 Subject: [PATCH 03/19] Old tests pass now --- src/RaidGeld.sol | 32 ++++++++++++++++++----------- src/RaidGeldStructs.sol | 3 ++- src/RaidGeldUtils.sol | 45 ++++++++++++++++++++++++----------------- test/RaidGeld.t.sol | 2 +- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 2dcce8b..8529188 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -66,13 +66,14 @@ contract RaidGeld is ERC20, Ownable, Constants { BUY_IN_DAO_TOKEN_AMOUNT = 500 * 10 ** daoToken.decimals(); } - function init_player(address player) private { + function start_game(address player) private { bool existing_player = players[player].is_registered; // Mint some starting tokens to the player _mint(player, INITIAL_GELD); // Reset or set player reset_player(player); + players[player].has_active_session = true; if (existing_player) { // TODO: Emit new run @@ -87,24 +88,26 @@ contract RaidGeld is ERC20, Ownable, Constants { uint32 current_n_runs = player.n_runs + 1; uint256 current_total_rewards = player.total_rewards; bool current_is_registered = player.is_registered; + uint32 current_prestige_level = player.prestige_level; // Set initial states - players[msg.sender] = Player({ + players[addr] = Player({ total_minted: INITIAL_GELD, created_at: block.timestamp, last_raided_at: block.timestamp, n_runs: current_n_runs, total_rewards: current_total_rewards, has_active_session: false, - is_registered: current_is_registered + is_registered: current_is_registered, + prestige_level: current_prestige_level }); - armies[msg.sender] = Army({ + armies[addr] = Army({ moloch_denier: Raider({level: 0}), apprentice: Raider({level: 0}), anointed: Raider({level: 0}), champion: Raider({level: 0}), profit_per_second: 0 }); - bosses[msg.sender] = Boss({level: 0, variant: 0}); + bosses[addr] = Boss({level: 0, boss_variants: RaidGeldUtils.generate_boss_variants(block.prevrandao)}); } // New player want to register with ETH @@ -122,7 +125,7 @@ contract RaidGeld is ERC20, Ownable, Constants { sqrtPriceLimitX96: 0 }); router.exactInputSingle(params); - init_player(msg.sender); + start_game(msg.sender); } // New player wants to register with dao @@ -132,8 +135,7 @@ contract RaidGeld is ERC20, Ownable, Constants { require( daoToken.transferFrom(msg.sender, address(this), BUY_IN_DAO_TOKEN_AMOUNT), "Failed to transfer DAO tokens" ); - // Init player - init_player(msg.sender); + start_game(msg.sender); } // Override for default number of decimals @@ -251,17 +253,23 @@ contract RaidGeld is ERC20, Ownable, Constants { uint256 geld_to_burn = balanceOf(msg.sender) >= RaidGeldUtils.getBossPower(boss_to_attack.level) ? RaidGeldUtils.getBossPower(boss_to_attack.level) : balanceOf(msg.sender); - 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); if (hasWonBattle) { // Burn geld, get some sweet DAO Token and continue _burn(msg.sender, geld_to_burn); uint256 reward = RaidGeldUtils.calculateBossReward(boss_to_attack.level); players[msg.sender].total_rewards += reward; daoToken.transferFrom(address(this), msg.sender, reward); + if (boss_to_attack.level == 6) { + // User ascends! Moloch is defeated + players[msg.sender].prestige_level += 1; + player_dies(msg.sender); + } else { + // else go to next boss + bosses[msg.sender].level += 1; + } } else { - // Whoops u die + // Whoops u died, boss defeated you player_dies(msg.sender); } } diff --git a/src/RaidGeldStructs.sol b/src/RaidGeldStructs.sol index 5d70a96..61d6703 100644 --- a/src/RaidGeldStructs.sol +++ b/src/RaidGeldStructs.sol @@ -19,11 +19,12 @@ struct Player { uint256 last_raided_at; uint256 total_rewards; uint32 n_runs; + uint32 prestige_level; bool is_registered; bool has_active_session; } struct Boss { - uint8 variant; uint8 level; + uint8[7] boss_variants; } diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index f793a44..c8da473 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -23,25 +23,25 @@ library RaidGeldUtils { function getBossPower(uint8 level) internal pure returns (uint256 power) { require(level <= 7, "Bosses only go to level 7"); - if (level == 0) return 9000000; // 900e4 - else if (level == 1) return 90000000; // 900e5 - else if (level == 2) return 900000000; // 900e6 - else if (level == 3) return 9000000000; // 900e7 - else if (level == 4) return 90000000000; // 900e8 - else if (level == 5) return 900000000000; // 900e9 - else if (level == 6) return 9000000000000; // 900e10 + if (level == 0) return 9000000; + else if (level == 1) return 90000000; + else if (level == 2) return 900000000; + else if (level == 3) return 9000000000; + else if (level == 4) return 90000000000; + else if (level == 5) return 900000000000; + else if (level == 6) return 9000000000000; } function getBossCumulativeChance(uint8 level) internal pure returns (uint256 chance) { // for boss chances (percent) [99, 89, 80, 70, 62, 51, 40] // where cumulative chance is in the form of 18 precision related to 1e18 require(level <= 7, "Bosses only go to level 7"); - if (level == 0) return 99e16; - else if (level == 1) return 8811e14; - else if (level == 2) return 7048e14; - else if (level == 3) return 493416e12; - else if (level == 4) return 30591792e10; - else if (level == 5) return 1560181392e8; + if (level == 0) return 99e16; + else if (level == 1) return 8811e14; + else if (level == 2) return 7048e14; + else if (level == 3) return 493416e12; + else if (level == 4) return 30591792e10; + else if (level == 5) return 1560181392e8; else if (level == 6) return 6240725568e7; } @@ -82,11 +82,7 @@ library RaidGeldUtils { } // Calculates whether user survives the fight - function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 prevrandao) - internal - pure - returns (bool) - { + function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 prevrandao) internal pure returns (bool) { uint256 bossPower = getBossPower(bossLevel); require(geldBurnt <= bossPower, "Cant try to defeat boss with more than what boss power is"); uint256 random_n = random(prevrandao, 1, 100); @@ -97,6 +93,19 @@ library RaidGeldUtils { return roll >= bossPower; } + function generate_boss_variants(uint256 prevrandao) internal pure returns (uint8[7] memory boss_variants) { + // We shuffle the possible variants so each run is a bit different + uint8[6] memory array = [0, 1, 2, 3, 4, 5]; + for (uint256 i = array.length - 1; i > 0; i--) { + // generate a pseudo-random index based on the prevrandao + uint256 j = uint256(keccak256(abi.encodePacked(prevrandao, i))) % (i + 1); + // swap elements at i and j + (array[i], array[j]) = (array[j], array[i]); + } + // Last boss is always the same old super Moloch + return [array[0], array[1], array[2], array[3], array[4], array[5], 6]; + } + // TODO: Implement actual randomness function random(uint256 prevrandao, uint256 min, uint256 max) internal pure returns (uint256) { // returns 0 - 100 diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index b58297c..c0bea26 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -42,7 +42,7 @@ contract raid_geldTest is Test, Constants { function fundAccount(address _acc) private { vm.deal(_acc, 10 ether); - stdstore.target(DAO_TOKEN).sig("balanceOf(address)").with_key(_acc).checked_write(100 ether); + stdstore.target(DAO_TOKEN).sig("balanceOf(address)").with_key(_acc).checked_write(1000 ether); } function registerPlayer() private { From 2e5d2143a89d49238f8a85102c001fa61b28b31c Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Wed, 30 Oct 2024 16:47:44 +0100 Subject: [PATCH 04/19] Boss rolls test --- src/RaidGeld.sol | 21 +++++++++----- src/RaidGeldStructs.sol | 2 +- src/RaidGeldUtils.sol | 31 ++++++++++++++++---- test/RaidGeld.t.sol | 30 +++++++++++++++++++- test/RaidGeldUtils.t.sol | 61 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 14 deletions(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 8529188..7c4e0bc 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -107,7 +107,7 @@ contract RaidGeld is ERC20, Ownable, Constants { champion: Raider({level: 0}), profit_per_second: 0 }); - bosses[addr] = Boss({level: 0, boss_variants: RaidGeldUtils.generate_boss_variants(block.prevrandao)}); + bosses[addr] = Boss({level: 0, variants: RaidGeldUtils.generate_boss_variants(block.prevrandao)}); } // New player want to register with ETH @@ -164,16 +164,21 @@ contract RaidGeld is ERC20, Ownable, Constants { emit RaidPerformed(player, players[player].total_minted, balanceOf(player)); } - // Function to get Player struct + // Function to get the Player struct function getPlayer(address addr) public view returns (Player memory) { return players[addr]; } - // Function to get Army struct + // Function to get the Army struct function getArmy(address addr) public view returns (Army memory) { return armies[addr]; } + // Function to get the Boss struct + function getBoss(address addr) public view returns (Boss memory) { + return bosses[addr]; + } + // Quick fn to check if user is registered function isRegistered(address addr) public view returns (bool) { return players[addr].created_at != 0; @@ -245,7 +250,7 @@ contract RaidGeld is ERC20, Ownable, Constants { ); } - function battle_with_boss() external onlyActiveSession { + function battle_with_boss() external onlyActiveSession returns (bool[2] memory hasWonOrAscended) { // first perform raid performRaid(msg.sender); Boss memory boss_to_attack = bosses[msg.sender]; @@ -255,15 +260,16 @@ contract RaidGeld is ERC20, Ownable, Constants { : balanceOf(msg.sender); bool hasWonBattle = RaidGeldUtils.calculateBossFight(boss_to_attack.level, geld_to_burn, block.prevrandao); if (hasWonBattle) { - // Burn geld, get some sweet DAO Token and continue + // Burn geld, send some sweet DAO Token and continue _burn(msg.sender, geld_to_burn); - uint256 reward = RaidGeldUtils.calculateBossReward(boss_to_attack.level); + uint256 reward = RaidGeldUtils.calculateBossReward(boss_to_attack.level, BUY_IN_DAO_TOKEN_AMOUNT); players[msg.sender].total_rewards += reward; daoToken.transferFrom(address(this), msg.sender, reward); if (boss_to_attack.level == 6) { - // User ascends! Moloch is defeated + // User ascends! Moloch is defeated, user can start a new run players[msg.sender].prestige_level += 1; player_dies(msg.sender); + return [hasWonBattle, true /* New prestige level! */ ]; } else { // else go to next boss bosses[msg.sender].level += 1; @@ -272,6 +278,7 @@ contract RaidGeld is ERC20, Ownable, Constants { // Whoops u died, boss defeated you player_dies(msg.sender); } + return [hasWonBattle, false /* hasnt gotten prestige level */ ]; } function player_dies(address player) private { diff --git a/src/RaidGeldStructs.sol b/src/RaidGeldStructs.sol index 61d6703..f30deed 100644 --- a/src/RaidGeldStructs.sol +++ b/src/RaidGeldStructs.sol @@ -26,5 +26,5 @@ struct Player { struct Boss { uint8 level; - uint8[7] boss_variants; + uint8[7] variants; } diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index c8da473..d92f6f0 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.13; import {Army} from "../src/RaidGeldStructs.sol"; +import {console} from "forge-std/Test.sol"; library RaidGeldUtils { uint256 public constant PRECISION = 10000; @@ -30,6 +31,7 @@ library RaidGeldUtils { else if (level == 4) return 90000000000; else if (level == 5) return 900000000000; else if (level == 6) return 9000000000000; + else return 0; } function getBossCumulativeChance(uint8 level) internal pure returns (uint256 chance) { @@ -38,11 +40,26 @@ library RaidGeldUtils { require(level <= 7, "Bosses only go to level 7"); if (level == 0) return 99e16; else if (level == 1) return 8811e14; - else if (level == 2) return 7048e14; + else if (level == 2) return 70488e13; else if (level == 3) return 493416e12; else if (level == 4) return 30591792e10; else if (level == 5) return 1560181392e8; else if (level == 6) return 6240725568e7; + else return 0; + } + + function getBossChance(uint8 level) internal pure returns (uint256 chance) { + // for boss chances (percent) [99, 89, 80, 70, 62, 51, 40] + // where cumulative chance is in the form of 18 precision related to 1e18 + require(level <= 7, "Bosses only go to level 7"); + if (level == 0) return 99; + else if (level == 1) return 89; + else if (level == 2) return 80; + else if (level == 3) return 70; + else if (level == 4) return 62; + else if (level == 5) return 51; + else if (level == 6) return 40; + else return 0; } function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) { @@ -77,20 +94,24 @@ library RaidGeldUtils { } // Returns how much Dao Token player is due for winning - function calculateBossReward(uint8 bossLevel) internal pure returns (uint256) { - return getBossPower(bossLevel) * (getBossCumulativeChance(bossLevel) * 10 / 5) ** 2; + function calculateBossReward(uint8 bossLevel, uint256 baseReward) internal pure returns (uint256) { + // 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); } // Calculates whether user survives the fight function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 prevrandao) internal pure returns (bool) { uint256 bossPower = getBossPower(bossLevel); + uint256 bossRoll = getBossChance(bossLevel); require(geldBurnt <= bossPower, "Cant try to defeat boss with more than what boss power is"); uint256 random_n = random(prevrandao, 1, 100); // Relative power as in, you can only put in 800 geld to defeat 900 geld boss, // but you will get exponentially worse chances uint256 relativePower = ((geldBurnt ** 2) * 100) / bossPower ** 2; - uint256 roll = (random_n * relativePower * PRECISION) / (100 * PRECISION); - return roll >= bossPower; + uint256 roll = random_n * relativePower / 1e2; + return roll < bossRoll; } function generate_boss_variants(uint256 prevrandao) internal pure returns (uint8[7] memory boss_variants) { diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index c0bea26..2bb65bd 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; import {stdStorage, StdStorage} from "forge-std/Test.sol"; -import {RaidGeld, Army, Player} from "../src/RaidGeld.sol"; +import {RaidGeld, Army, Player, Boss} from "../src/RaidGeld.sol"; import "../src/RaidGeldUtils.sol"; import {Constants} from "../src/Constants.sol"; @@ -285,4 +285,32 @@ contract raid_geldTest is Test, Constants { assertLt(newBalance, newestBalance); assertLt(last_raided_at, last_raided_at_2); } + + function test_08_attack_boss() public { + // Let some time pass so we dont start at block timestamp 0 + vm.warp(120); + + // Register player 1 + vm.startPrank(player1); + registerPlayer(); + raid_geld.addUnit(0, 1); + + Boss memory boss = raid_geld.getBoss(player1); + // assert boss is initialized + assertEq(boss.level, 0); + // make sure variants shuffled + assertNotEq(boss.variants[1], boss.variants[2]); + + // Make a lot of time pass so user deffo has GELD to attack the boss + vm.warp(1200000); + + bool[2] memory results = raid_geld.battle_with_boss(); + console.log(results[0]); + console.log(results[1]); + + // Should almost always defeat first boss + assertEq(results[0], true); + // First boss doesnt grant a new prestige level (ascension) + assertEq(results[1], false); + } } diff --git a/test/RaidGeldUtils.t.sol b/test/RaidGeldUtils.t.sol index fbda317..7502ba9 100644 --- a/test/RaidGeldUtils.t.sol +++ b/test/RaidGeldUtils.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; import {Army, Raider} from "../src/RaidGeldStructs.sol"; import "../src/RaidGeldUtils.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; contract raid_geldTest is Test { function test_0_unit_price() public pure { @@ -51,4 +52,64 @@ contract raid_geldTest is Test { + _anLvl * RaidGeldUtils.ANOINTED_PROFIT + _cLvl * RaidGeldUtils.CHAMPION_PROFIT; assertEq(profits_per_second, expected); } + + function getConfidenceInterval(uint8 bossLevel) + internal + pure + returns (uint256 lower, uint256 upper) + { + /* + man i struggled getting this math with just integers so its + easier to just do it like this (precalculated): + z = 1.96 (95%) + Probability | Expected | Variance | Margin | Lower Bound | Upper Bound + 99% 990 0.00099 62 928 1052 + 89% 890 0.000979 61 829 951 + 80% 800 0.00016 49 751 849 + 70% 700 0.00021 45 655 745 + 62% 620 0.0002356 48 572 668 + 51% 510 0.0002499 49 461 559 + 40% 400 0.00024 48 352 448 + */ + if (bossLevel == 0) { + return (928, 1000); + } else if (bossLevel == 1) { + return (829, 951); + } else if (bossLevel == 2) { + return (751, 849); + } else if (bossLevel == 3) { + return (655, 745); + } else if (bossLevel == 4) { + return (572, 668); + } else if (bossLevel == 5) { + return (461, 559); + } else if (bossLevel == 6) { + return (352, 448); + } + } + + function test_2_calculateBossFight_probabilities() public { + uint256[7] memory probabilities = + [uint256(99), uint256(89), uint256(80), uint256(70), uint256(62), uint256(51), uint256(40)]; + uint256 totalRuns = 1000; + console.log("Checking boss rolls (1000 times)"); + for (uint8 bossLevel = 0; bossLevel < 7; bossLevel++) { + console.log("Boss level: ", bossLevel); + uint256 bossPower = RaidGeldUtils.getBossPower(bossLevel); + uint256 geldBurnt = bossPower; + uint256 successCount = 0; + for (uint256 testRun = 0; testRun < totalRuns; testRun++) { + uint256 prevrandao = uint256(keccak256(abi.encodePacked(block.prevrandao, testRun * 1e9))); // Unique seed each time + if (RaidGeldUtils.calculateBossFight(bossLevel, geldBurnt, prevrandao)) { + successCount++; + } + } + (uint256 lowerProb, uint256 upperProb) = getConfidenceInterval(bossLevel); + console.log("expected at least wins", lowerProb); + console.log("expected at most wins", upperProb); + console.log("actual wins", successCount); + console.log("----------"); + vm.assertTrue(successCount >= lowerProb && successCount <= upperProb, "Success rate not within expected range"); + } + } } From 44f3e97de8dabcd0c05ac702b02381d58d8b396f Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Wed, 30 Oct 2024 19:14:22 +0100 Subject: [PATCH 05/19] Additional randomness tests plus removed withdraw --- src/RaidGeld.sol | 8 +------- src/RaidGeldUtils.sol | 6 +++--- test/RaidGeld.t.sol | 41 +++++----------------------------------- test/RaidGeldUtils.t.sol | 28 ++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 47 deletions(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 7c4e0bc..8c95e8c 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -143,12 +143,6 @@ contract RaidGeld is ERC20, Ownable, Constants { return 4; } - // Allows the owner to withdraw DAO tokens - function withdraw() external onlyOwner { - uint256 amount = daoToken.balanceOf(address(this)); - daoToken.transfer(owner(), amount); - } - // Manual minting for itchy fingers function raid() external onlyActiveSession { performRaid(msg.sender); @@ -264,7 +258,7 @@ contract RaidGeld is ERC20, Ownable, Constants { _burn(msg.sender, geld_to_burn); uint256 reward = RaidGeldUtils.calculateBossReward(boss_to_attack.level, BUY_IN_DAO_TOKEN_AMOUNT); players[msg.sender].total_rewards += reward; - daoToken.transferFrom(address(this), msg.sender, reward); + daoToken.transfer(msg.sender, reward); if (boss_to_attack.level == 6) { // User ascends! Moloch is defeated, user can start a new run players[msg.sender].prestige_level += 1; diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index d92f6f0..5f42d1f 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.13; import {Army} from "../src/RaidGeldStructs.sol"; -import {console} from "forge-std/Test.sol"; library RaidGeldUtils { uint256 public constant PRECISION = 10000; @@ -98,7 +97,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); + return (baseReward * rewardMultiplier) / 1e18; } // Calculates whether user survives the fight @@ -130,7 +129,8 @@ library RaidGeldUtils { // TODO: Implement actual randomness function random(uint256 prevrandao, uint256 min, uint256 max) internal pure returns (uint256) { // returns 0 - 100 - require(max >= min, "Max must be greater than or equal to min"); + require(max > min, "Max must be greater than min"); + require(max < type(uint256).max, "Dont use largest possible uint"); uint256 range = max - min + 1; return min + (uint256(keccak256(abi.encodePacked(prevrandao))) % range); } diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index 2bb65bd..62ff7e6 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -134,38 +134,7 @@ contract raid_geldTest is Test, Constants { assertEq(army.champion.level, 0); } - function test_03_dao_token_can_be_withdrawn() public { - uint256 initialBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); - - // Switch to Player 1 and register it - vm.startPrank(player1); - - // doesnt test player emitted event because other events get emitted before it - registerPlayerWithDaoToken(); - - // Switch back to owner and withdraw funds - vm.startPrank(owner); - raid_geld.withdraw(); - uint256 newBalance = raid_geld.daoToken().balanceOf(address(owner)); - uint256 newContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); - - // contract balance should be empty - assertEq(newContractBalance, 0); - // owner should have the extra funds - assertGt(newBalance, initialBalance); - } - - function test_04_only_owner_can_withdraw() public { - // Register player 1 - vm.startPrank(player1); - registerPlayer(); - - // attempt to withdraw with player 1, it should fail - vm.expectRevert(); - raid_geld.withdraw(); - } - - function test_05_is_registered() public { + function test_03_is_registered() public { bool is_registered = raid_geld.isRegistered(player1); assertEq(is_registered, false); vm.startPrank(player1); @@ -179,7 +148,7 @@ contract raid_geldTest is Test, Constants { assertEq(is_registered, true); } - function test_06_add_unit() public { + function test_04_add_unit() public { vm.startPrank(player1); // Making sure event is emitted when player is registered @@ -229,7 +198,7 @@ contract raid_geldTest is Test, Constants { assertLt(income_per_sec, new_income_per_sec); } - function test_07_raid() public { + function test_05_raid() public { // Let some time pass so we dont start at block timestamp 0 vm.warp(120); @@ -286,13 +255,13 @@ contract raid_geldTest is Test, Constants { assertLt(last_raided_at, last_raided_at_2); } - function test_08_attack_boss() public { + function test_06_attack_boss() public { // Let some time pass so we dont start at block timestamp 0 vm.warp(120); // Register player 1 vm.startPrank(player1); - registerPlayer(); + registerPlayerWithDaoToken(); raid_geld.addUnit(0, 1); Boss memory boss = raid_geld.getBoss(player1); diff --git a/test/RaidGeldUtils.t.sol b/test/RaidGeldUtils.t.sol index 7502ba9..bedb1f4 100644 --- a/test/RaidGeldUtils.t.sol +++ b/test/RaidGeldUtils.t.sol @@ -88,7 +88,33 @@ contract raid_geldTest is Test { } } - function test_2_calculateBossFight_probabilities() public { + function test_21_random(uint256 seed, uint256 min, uint256 max) public pure { + vm.assume(min < max); + vm.assume(max < type(uint256).max); + uint256 random = RaidGeldUtils.random(seed, min, max); + vm.assertTrue(random >= min && random <= max, "random() fell outside its range"); + } + + function test_22_random_range(uint256 min, uint256 max) public { + vm.assume(max > min); + vm.assume(max - min < 100); + vm.assume(max < type(uint256).max); + uint256 range = max - min + 1; + bool[] memory seen = new bool[](range); + uint256 count = 0; + for (uint256 i = 0; i < 100000; i++) { + uint256 result = RaidGeldUtils.random(block.prevrandao + i, min, max); + uint256 index = result - min; + if (!seen[index]) { + seen[index] = true; + count++; + } + if (count == range) break; + } + assertEq(count, range, "Not all values in the interval were generated"); + } + + function test_3_calculateBossFight_probabilities() public { uint256[7] memory probabilities = [uint256(99), uint256(89), uint256(80), uint256(70), uint256(62), uint256(51), uint256(40)]; uint256 totalRuns = 1000; From e282b499e91082d1c1de35e81a60cfd3b9c3e13f Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Wed, 30 Oct 2024 20:44:00 +0100 Subject: [PATCH 06/19] Player loses test --- src/RaidGeld.sol | 2 +- src/RaidGeldUtils.sol | 3 ++- test/RaidGeld.t.sol | 44 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 8c95e8c..0852cb6 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -12,7 +12,7 @@ contract RaidGeld is ERC20, Ownable, Constants { uint256 public constant MANTISSA = 1e4; uint256 public constant BUY_IN_AMOUNT = 0.00005 ether; uint256 public immutable BUY_IN_DAO_TOKEN_AMOUNT; - uint256 public constant INITIAL_GELD = 500 * MANTISSA; + uint256 public constant INITIAL_GELD = 50 * MANTISSA; mapping(address => Player) private players; mapping(address => Army) private armies; mapping(address => Boss) private bosses; diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index 5f42d1f..0296489 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.13; import {Army} from "../src/RaidGeldStructs.sol"; +import {console} from "forge-std/Test.sol"; library RaidGeldUtils { uint256 public constant PRECISION = 10000; @@ -109,7 +110,7 @@ library RaidGeldUtils { // Relative power as in, you can only put in 800 geld to defeat 900 geld boss, // but you will get exponentially worse chances uint256 relativePower = ((geldBurnt ** 2) * 100) / bossPower ** 2; - uint256 roll = random_n * relativePower / 1e2; + uint256 roll = 1e2 - (random_n * relativePower / 1e2); return roll < bossRoll; } diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index 62ff7e6..ba9b97a 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -264,6 +264,9 @@ contract raid_geldTest is Test, Constants { registerPlayerWithDaoToken(); raid_geld.addUnit(0, 1); + uint256 initialDaoBalance = raid_geld.daoToken().balanceOf(player1); + uint256 initialContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); + Boss memory boss = raid_geld.getBoss(player1); // assert boss is initialized assertEq(boss.level, 0); @@ -272,14 +275,47 @@ contract raid_geldTest is Test, Constants { // Make a lot of time pass so user deffo has GELD to attack the boss vm.warp(1200000); - bool[2] memory results = raid_geld.battle_with_boss(); - console.log(results[0]); - console.log(results[1]); - // Should almost always defeat first boss assertEq(results[0], true); // First boss doesnt grant a new prestige level (ascension) assertEq(results[1], false); + + uint256 afterBossDaoBalance = raid_geld.daoToken().balanceOf(player1); + uint256 afterBossContractBalance = raid_geld.daoToken().balanceOf(address(raid_geld)); + + // User should receive funs, contract should lose them + assertLt(initialDaoBalance, afterBossDaoBalance); + assertGt(initialContractBalance, afterBossContractBalance); + + Player memory player = raid_geld.getPlayer(player1); + // Players total rewards should increase + assertGt(player.total_rewards, 0); + } + + function test_07_attack_boss_fail() public { + // Let some time pass so we dont start at block timestamp 0 + vm.warp(120); + + // Register player 1 + vm.startPrank(player1); + registerPlayerWithDaoToken(); + raid_geld.addUnit(0, 1); + + bool[2] memory results = raid_geld.battle_with_boss(); + // Should lose with just starting GELD + assertEq(results[0], false); + // First boss doesnt grant a new prestige level (ascension) + assertEq(results[1], false); + + Player memory player = raid_geld.getPlayer(player1); + Army memory army = raid_geld.getArmy(player1); + + // player sessions should end + assertEq(player.has_active_session, false); + // player should lose all geld + assertEq(raid_geld.balanceOf(player1), 0); + // Units should reset + assertEq(army.moloch_denier.level, 0); } } From b79e9c7eb03bc0c91944bd1c332f9c4f0558016b Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Wed, 30 Oct 2024 21:10:01 +0100 Subject: [PATCH 07/19] Player can reenter the game test --- src/RaidGeld.sol | 1 + test/RaidGeld.t.sol | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 0852cb6..43a91be 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -74,6 +74,7 @@ contract RaidGeld is ERC20, Ownable, Constants { // Reset or set player reset_player(player); players[player].has_active_session = true; + players[player].is_registered = true; if (existing_player) { // TODO: Emit new run diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index ba9b97a..5f2fb1f 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -288,6 +288,10 @@ contract raid_geldTest is Test, Constants { assertLt(initialDaoBalance, afterBossDaoBalance); assertGt(initialContractBalance, afterBossContractBalance); + // Boss should level up + boss = raid_geld.getBoss(player1); + assertEq(boss.level, 1); + Player memory player = raid_geld.getPlayer(player1); // Players total rewards should increase assertGt(player.total_rewards, 0); @@ -318,4 +322,32 @@ contract raid_geldTest is Test, Constants { // Units should reset assertEq(army.moloch_denier.level, 0); } + + function test_08_player_who_lost_can_restart() public { + // Let some time pass so we dont start at block timestamp 0 + vm.warp(120); + uint256 balance1 = raid_geld.daoToken().balanceOf(address(raid_geld)); + + // Register player 1 + vm.startPrank(player1); + registerPlayerWithDaoToken(); + raid_geld.addUnit(0, 1); + uint256 balance2 = raid_geld.daoToken().balanceOf(address(raid_geld)); + // Contract gets DAO Tokens with first register + assertLt(balance1, balance2); + + bool[2] memory results = raid_geld.battle_with_boss(); + // Should lose with just starting GELD + assertEq(results[0], false); + Player memory player = raid_geld.getPlayer(player1); + // player sessions should end + assertEq(player.has_active_session, false); + assertEq(player.is_registered, true); + + registerPlayerWithDaoToken(); + player = raid_geld.getPlayer(player1); + assertEq(player.has_active_session, true); + uint256 balance3 = raid_geld.daoToken().balanceOf(address(raid_geld)); + assertLt(balance2, balance3); + } } From ae8cf3781a638ad77835a54cfa221f813c8c0b81 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Wed, 30 Oct 2024 22:19:13 +0100 Subject: [PATCH 08/19] Proves the game is winnable --- src/RaidGeld.sol | 3 ++- test/RaidGeld.t.sol | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 43a91be..426e45b 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -75,6 +75,7 @@ contract RaidGeld is ERC20, Ownable, Constants { reset_player(player); players[player].has_active_session = true; players[player].is_registered = true; + players[player].n_runs += 1; if (existing_player) { // TODO: Emit new run @@ -86,7 +87,7 @@ contract RaidGeld is ERC20, Ownable, Constants { function reset_player(address addr) private { Player memory player = players[addr]; - uint32 current_n_runs = player.n_runs + 1; + uint32 current_n_runs = player.n_runs; uint256 current_total_rewards = player.total_rewards; bool current_is_registered = player.is_registered; uint32 current_prestige_level = player.prestige_level; diff --git a/test/RaidGeld.t.sol b/test/RaidGeld.t.sol index 5f2fb1f..1d32a09 100644 --- a/test/RaidGeld.t.sol +++ b/test/RaidGeld.t.sol @@ -350,4 +350,49 @@ contract raid_geldTest is Test, Constants { uint256 balance3 = raid_geld.daoToken().balanceOf(address(raid_geld)); assertLt(balance2, balance3); } + + function test_09_player_can_gain_prestige(uint256 prevrandao) public { + vm.assume(prevrandao < type(uint256).max - 1e4); + vm.startPrank(player1); + stdstore.target(DAO_TOKEN).sig("balanceOf(address)").with_key(player1).checked_write(10000000 ether); + stdstore.target(DAO_TOKEN).sig("balanceOf(address)").with_key(address(raid_geld)).checked_write(10000000 ether); + bool success = false; + uint256 tries = 0; + uint256 streak = 0; + for (uint i = 0; i < 1000; i++) { + vm.prevrandao(prevrandao + i * 59); + bool[2] memory results; + bool alreadyLost = false; + uint256 newStreak = 0; + tries += 1; + console.log("NEW TRY, number: ", tries); + registerPlayerWithDaoToken(); + // Give bajillion GELD to player so player can battle boss after boss + stdstore.target(address(raid_geld)).sig("balanceOf(address)").with_key(player1).checked_write(10000000 ether); + for (uint j = 0; j < 6; j++) { + vm.prevrandao(prevrandao + j * 7 + i * 59); + newStreak += 1; + results = raid_geld.battle_with_boss(); + if (results[0] == false) { + alreadyLost = true; + break; + } + } + if (alreadyLost) { + if (newStreak > streak) { + streak = newStreak; + } + continue; + } + results = raid_geld.battle_with_boss(); + if (results[0] == true && results[1] == true) { + success = true; + Player memory player = raid_geld.getPlayer(player1); + vm.assertEq(player.prestige_level, 1); + vm.assertEq(player.n_runs, tries); + break; + } + } + require(success, "Player should eventually succeed"); + } } From 3504059ba878defe6c8cb4b31b1ca78afc7ccdae Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Wed, 30 Oct 2024 23:15:46 +0100 Subject: [PATCH 09/19] Added new sprites to the game --- app/public/background/boss/0_gluttony.svg | 422 ++++ app/public/background/boss/1_sloth.svg | 676 ++++++ app/public/background/boss/2_lust.svg | 939 ++++++++ app/public/background/boss/3_wrath.svg | 829 +++++++ app/public/background/boss/4_envy.svg | 722 +++++++ app/public/background/boss/5_pride.svg | 387 ++++ app/public/background/boss/6_greed.svg | 1554 ++++++++++++++ .../background/mountains/0_gluttony.svg | 908 ++++++++ app/public/background/mountains/1_sloth.svg | 1902 +++++++++++++++++ app/public/background/mountains/2_lust.svg | 625 ++++++ app/public/background/mountains/3_wrath.svg | 1639 ++++++++++++++ app/public/background/mountains/4_envy.svg | 1397 ++++++++++++ app/public/background/mountains/5_pride.svg | 1158 ++++++++++ app/public/background/mountains/6_greed.svg | 632 ++++++ app/public/background/tower/0_gluttony.svg | 403 ++++ app/public/background/tower/1_sloth.svg | 539 +++++ app/public/background/tower/2_lust.svg | 518 +++++ app/public/background/tower/3_wrath.svg | 902 ++++++++ app/public/background/tower/4_envy.svg | 494 +++++ app/public/background/tower/5_pride.svg | 494 +++++ app/public/background/tower/6_greed.svg | 962 +++++++++ 21 files changed, 18102 insertions(+) create mode 100644 app/public/background/boss/0_gluttony.svg create mode 100644 app/public/background/boss/1_sloth.svg create mode 100644 app/public/background/boss/2_lust.svg create mode 100644 app/public/background/boss/3_wrath.svg create mode 100644 app/public/background/boss/4_envy.svg create mode 100644 app/public/background/boss/5_pride.svg create mode 100644 app/public/background/boss/6_greed.svg create mode 100644 app/public/background/mountains/0_gluttony.svg create mode 100644 app/public/background/mountains/1_sloth.svg create mode 100644 app/public/background/mountains/2_lust.svg create mode 100644 app/public/background/mountains/3_wrath.svg create mode 100644 app/public/background/mountains/4_envy.svg create mode 100644 app/public/background/mountains/5_pride.svg create mode 100644 app/public/background/mountains/6_greed.svg create mode 100644 app/public/background/tower/0_gluttony.svg create mode 100644 app/public/background/tower/1_sloth.svg create mode 100644 app/public/background/tower/2_lust.svg create mode 100644 app/public/background/tower/3_wrath.svg create mode 100644 app/public/background/tower/4_envy.svg create mode 100644 app/public/background/tower/5_pride.svg create mode 100644 app/public/background/tower/6_greed.svg diff --git a/app/public/background/boss/0_gluttony.svg b/app/public/background/boss/0_gluttony.svg new file mode 100644 index 0000000..0583a04 --- /dev/null +++ b/app/public/background/boss/0_gluttony.svg @@ -0,0 +1,422 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/boss/1_sloth.svg b/app/public/background/boss/1_sloth.svg new file mode 100644 index 0000000..9b046cb --- /dev/null +++ b/app/public/background/boss/1_sloth.svg @@ -0,0 +1,676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/boss/2_lust.svg b/app/public/background/boss/2_lust.svg new file mode 100644 index 0000000..04def42 --- /dev/null +++ b/app/public/background/boss/2_lust.svg @@ -0,0 +1,939 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/boss/3_wrath.svg b/app/public/background/boss/3_wrath.svg new file mode 100644 index 0000000..5cdbba7 --- /dev/null +++ b/app/public/background/boss/3_wrath.svg @@ -0,0 +1,829 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/boss/4_envy.svg b/app/public/background/boss/4_envy.svg new file mode 100644 index 0000000..71964a9 --- /dev/null +++ b/app/public/background/boss/4_envy.svg @@ -0,0 +1,722 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/boss/5_pride.svg b/app/public/background/boss/5_pride.svg new file mode 100644 index 0000000..1612460 --- /dev/null +++ b/app/public/background/boss/5_pride.svg @@ -0,0 +1,387 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/boss/6_greed.svg b/app/public/background/boss/6_greed.svg new file mode 100644 index 0000000..4e2d256 --- /dev/null +++ b/app/public/background/boss/6_greed.svg @@ -0,0 +1,1554 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/mountains/0_gluttony.svg b/app/public/background/mountains/0_gluttony.svg new file mode 100644 index 0000000..faa2585 --- /dev/null +++ b/app/public/background/mountains/0_gluttony.svg @@ -0,0 +1,908 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/mountains/1_sloth.svg b/app/public/background/mountains/1_sloth.svg new file mode 100644 index 0000000..b5d0874 --- /dev/null +++ b/app/public/background/mountains/1_sloth.svg @@ -0,0 +1,1902 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/mountains/2_lust.svg b/app/public/background/mountains/2_lust.svg new file mode 100644 index 0000000..5777728 --- /dev/null +++ b/app/public/background/mountains/2_lust.svg @@ -0,0 +1,625 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/mountains/3_wrath.svg b/app/public/background/mountains/3_wrath.svg new file mode 100644 index 0000000..e3dd507 --- /dev/null +++ b/app/public/background/mountains/3_wrath.svg @@ -0,0 +1,1639 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/mountains/4_envy.svg b/app/public/background/mountains/4_envy.svg new file mode 100644 index 0000000..88f1f4c --- /dev/null +++ b/app/public/background/mountains/4_envy.svg @@ -0,0 +1,1397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/mountains/5_pride.svg b/app/public/background/mountains/5_pride.svg new file mode 100644 index 0000000..34b32a6 --- /dev/null +++ b/app/public/background/mountains/5_pride.svg @@ -0,0 +1,1158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/mountains/6_greed.svg b/app/public/background/mountains/6_greed.svg new file mode 100644 index 0000000..01e7a7d --- /dev/null +++ b/app/public/background/mountains/6_greed.svg @@ -0,0 +1,632 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/tower/0_gluttony.svg b/app/public/background/tower/0_gluttony.svg new file mode 100644 index 0000000..3fc4f6b --- /dev/null +++ b/app/public/background/tower/0_gluttony.svg @@ -0,0 +1,403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/tower/1_sloth.svg b/app/public/background/tower/1_sloth.svg new file mode 100644 index 0000000..216815f --- /dev/null +++ b/app/public/background/tower/1_sloth.svg @@ -0,0 +1,539 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/tower/2_lust.svg b/app/public/background/tower/2_lust.svg new file mode 100644 index 0000000..cacb2e6 --- /dev/null +++ b/app/public/background/tower/2_lust.svg @@ -0,0 +1,518 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/tower/3_wrath.svg b/app/public/background/tower/3_wrath.svg new file mode 100644 index 0000000..20a4c2a --- /dev/null +++ b/app/public/background/tower/3_wrath.svg @@ -0,0 +1,902 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/tower/4_envy.svg b/app/public/background/tower/4_envy.svg new file mode 100644 index 0000000..c95ee68 --- /dev/null +++ b/app/public/background/tower/4_envy.svg @@ -0,0 +1,494 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/tower/5_pride.svg b/app/public/background/tower/5_pride.svg new file mode 100644 index 0000000..c95ee68 --- /dev/null +++ b/app/public/background/tower/5_pride.svg @@ -0,0 +1,494 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/background/tower/6_greed.svg b/app/public/background/tower/6_greed.svg new file mode 100644 index 0000000..8040add --- /dev/null +++ b/app/public/background/tower/6_greed.svg @@ -0,0 +1,962 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 86ad9c5e1a190ee4bdf76d20eaafd83bbd5b9b84 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Thu, 31 Oct 2024 03:37:31 +0100 Subject: [PATCH 10/19] 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 zcmV;R3t#k!P)IqV^Sfd~keDbC0H-Fvnxz4hjA+sBSiz%UGa`pI?p171wc9ET{1xc293 zxcuA+IC^B5MWEF(V6{O`dTBhx&Z5S}j=Hj3H8N z;^M`p;c&T;D-^N2wTJ8{Ru)dWBz%MphI6N9a zw$Q+l0XH&{ee5inp!4&FrlBDS$fXLf^K&}8Ns2h#lAhj_BQ3VV+g_ZODX&};B|T&UG`6pCeBd438Ww+)IcK(;CHc-wFpCMdkE zWNJfEy)er%+FBJ(Q9?GAL!sKh%HlRMl`6uS6yE;pm#`=zM^HjKkwhe%h1Dh_mdrt# znwi46v&SHD#`$6cmw)sO1_lREt&}nU?OiM_ujA6GAs9^&ckV^uR&AW86SYzigCqUe z5658PE&W~96Xk+BhWDAgTttx>|{(Q!znLJoq^OBS^GW)qD@ z3&~_27cR}>*7YTxyyuFBUJdcgF4c@qe zcnR_SB4odcc&v$bEYqcC+p1zIs=+3^a4*_|5h->lY!U^SIY)`?F;eC3;rW_~%4QP3 z|IORbszu!TW}YRB_~w(F1SOz2T#%0RJMi7yIuYIvXF$M-Q8SX6CZg#Q+71alZYzdI zL)avzXQoGp=r$_VDjq%D#_5w2XpjMq*P@sl3!=p?YE2zJpA+MgA^h{5>j;s4{Yn)h zlRnIEq;P6>9Lw`d=;vo_lcSJEhI-LwjKb!$BfA>InUmv4b53hJ1*}9G_{r`D7O3qd|1&;BYu#6^lHqiCZ_8AX{v>erI*X|MlbR zExk@jFgQctcYpiPIG-c;%PB_+e;oF9Da>B6Z!U)EWe-3`s&) zQ#6fFswPOe3ej!SVA30ynHoWvN>$2L3Ai%VQ0xM1ay)>g&Ghw;KYQm59N_ih<)4qu z%#MY2H^WG5MeyC@1e~fFb&lus`93^#B*;SRJi7_$%(-cpqmPgvcr1zn$YxXpz)rrm z@}v~oX0cF&$nk@|+Vq87F2^o)n57{?4>)^j8jm-^(24$HL1RG_p0>e;vnSP>MrEU# zvBD}l(rKfU2Bc9HJ37jaL#Egq^wqgp2Pd@#<(v=CjCpyl76QG#E=CH4D!%zH1#_SU z38I2VTj){=W((5UGF%P|!Bg!x05*2^NlEH_u5{o6%H=9P`d|*xXc9rYg7xiGm)Zgq z(l1>70 z%h_{8PF3aat%Hw%H!KuOT}|ldhSy_fo-ko&YY)RyUepVDl(ShhbqA#Xee^ZtMh=y9 z7SFxlp}8b!$#two^Jq&>+?rd(!tx&K+QBhU%4hM}rw_2TLmCRCCMCY7Ey6;zl_-Jr zS{?DNUBG=5l45Eg9f@LsVxd#jI^FP5{gS07mSY;uo_~rWGmLBhx^v(Hf&;xcIX#R< zg_;mcz)nonr~?5yW|NXEvC{%IhCu?-(7)Hvj5oWA=k|Cgt(F59P_5M2P5J^gToyb3XP4VlTxSo*Q$`_Eh9;P?xWC*ffbHNz zTf12tR(+UY?CM~k#1XV2dE9@ni5>=!*zWc#KX~@Fu}%`Q^3+H)0oBFAOr0!{rN@X}G8ZiNBXgeS2y1{upFGR^1nKx(#3T@zt9=+pFd{-W@p z-E1eA3OF)3h`xZzXm&C*HtY*ID5)J`T3UACxlUqs^xzMFdJjMS*#$UMD>I)H^K(zo z-={*giJ0GwB9pJe#tsh*2J!j)9R!aIAeG3YXDCE3$XBqvoIseUcldj8cVQhIFCur9b7>`vQO31sIF1JC zSjg$visaD0N4Aw4xVIF;(sm4|lpv&gi($^g38T-6_wVe$?N)Is+Jxd7#NK)Y!zLYb zpU*K9HoGCCTtqi^sr;sVG~U&UL+&2zJ_$3pl4|r4pTY=7^xFuacuR)E8$g^4i)YM8 z=_*1Vm2usJ4W=sT>g!kGakMDAZT#oMIb411d3^QREz)laZ@u{!c!UZj`(z4|r3)ap z*8vA@wN$Lryv*277O+aJoE-Au(USy*4+k(3^mg5}b8CZbf8*CbMn0S4ZU8JUM)2}W zCt0`&|9JNXB#pNzv?}N&^<)Z#h2DSV$}G0F61=re09gU=YZYlf5`L8N|JZTa0%G4uzbgu7S<91T)10y!7%J?8gf1kVwTfb%EYvcS2fX z$b9y~EW*2KnnxVVi>q*{3fjy9ZRWy5)bN4f0mO*jMkiD#o!nk@ z1s&mVn1|9uXz>Ma{|K}LfYkveJk!TcjKKqHv4 z1YN8PtQym8p-^M`rLy%UDWftxR5+KViV<=)^w zg!A82~jRMCWlDynLvEPI~7ElV1Bq1XR>Z$?a6)qO@Q)nuS?$=FZ+Ky 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); + } } From 9e15e0eb2afc0f2f2fe7ac0637dfdd2989bd8bff Mon Sep 17 00:00:00 2001 From: yellow <8539006+yellowBirdy@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:21:08 +0100 Subject: [PATCH 11/19] small refactors --- src/RaidGeld.sol | 54 +++++++++++++++++++++---------------------- src/RaidGeldUtils.sol | 21 +++++++++-------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index efb9d0d..1da2f84 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -10,8 +10,8 @@ import "../src/Constants.sol"; contract RaidGeld is ERC20, Ownable, Constants { uint256 public constant MANTISSA = 1e4; - uint256 public constant BUY_IN_AMOUNT = 0.0005 ether; - uint256 public immutable BUY_IN_DAO_TOKEN_AMOUNT; + uint256 public constant BUY_IN_AMOUNT = 0.00005 ether; + uint256 public BUY_IN_DAO_TOKEN_AMOUNT; uint256 public constant INITIAL_GELD = 50 * MANTISSA; mapping(address => Player) private players; mapping(address => Army) private armies; @@ -28,6 +28,7 @@ contract RaidGeld is ERC20, Ownable, Constants { // Events event PlayerRegistered(address indexed player, uint256 initialGeld); + event PleyerStrikesAgain(address indexed player, uint256 totalRewards, uint256 initialGeld); event RaidPerformed(address indexed player, uint256 totalMinted, uint256 geldBalance); event UnitAdded( address indexed player, @@ -40,6 +41,7 @@ contract RaidGeld is ERC20, Ownable, Constants { uint16 anointedLevel, uint16 championLevel ); + event DaoTokenBuyInAmountSet(address indexed owner, uint256 oldAmount, uint256 newAmount); // Modifier for functions that should only be available to registered players modifier onlyPlayer() { @@ -53,7 +55,7 @@ contract RaidGeld is ERC20, Ownable, Constants { } modifier newPlay() { - Player memory player = players[msg.sender]; + Player storage player = players[msg.sender]; bool notRegistered = player.created_at == 0; bool returningPlayer = player.is_registered && !player.has_active_session; require(notRegistered || returningPlayer, "Active session already in progress"); @@ -72,44 +74,37 @@ contract RaidGeld is ERC20, Ownable, Constants { // Mint some starting tokens to the player _mint(player, INITIAL_GELD); // Reset or set player - reset_player(player); + resetPlayer(player); players[player].has_active_session = true; players[player].is_registered = true; players[player].n_runs += 1; if (existing_player) { // TODO: Emit new run + emit PleyerStrikesAgain(player, players[player].total_rewards, INITIAL_GELD); } else { // Emit event emit PlayerRegistered(msg.sender, INITIAL_GELD); } } - function reset_player(address addr) private { - Player memory player = players[addr]; - uint32 current_n_runs = player.n_runs; - uint256 current_total_rewards = player.total_rewards; - bool current_is_registered = player.is_registered; - uint32 current_prestige_level = player.prestige_level; + function resetPlayer(address _playerAddress) private { + Player storage player = players[_playerAddress]; // Set initial states - players[addr] = Player({ - total_minted: INITIAL_GELD, - created_at: block.timestamp, - last_raided_at: block.timestamp, - n_runs: current_n_runs, - total_rewards: current_total_rewards, - has_active_session: false, - is_registered: current_is_registered, - prestige_level: current_prestige_level - }); - armies[addr] = Army({ + + player.total_minted = INITIAL_GELD; + player.created_at = block.timestamp; + player.last_raided_at = block.timestamp; + player.has_active_session = false; + + armies[_playerAddress] = Army({ moloch_denier: Raider({level: 0}), apprentice: Raider({level: 0}), anointed: Raider({level: 0}), champion: Raider({level: 0}), profit_per_second: 0 }); - bosses[addr] = Boss({level: 0, variants: RaidGeldUtils.generate_boss_variants(block.prevrandao)}); + bosses[_playerAddress] = Boss({level: 0, variants: RaidGeldUtils.generate_boss_variants(block.prevrandao)}); } // New player want to register with ETH @@ -202,14 +197,11 @@ contract RaidGeld is ERC20, Ownable, Constants { uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units); - // First trigger a raid so player receives what he is due at to this moment - uint256 time_past = block.timestamp - players[msg.sender].last_raided_at; - uint256 new_geld = armies[msg.sender].profit_per_second * time_past; - require(balanceOf(msg.sender) + new_geld >= cost, "Not enough GELD to add this unit"); + performRaid(msg.sender); // TODO: Since we are first minting then burning the token, this could be simplified - // by first calculating the difference and then minting / burning in just one operation + require(balanceOf(msg.sender) >= cost, "Not enough GELD to add this unit"); // then burn the cost of the new army _burn(msg.sender, cost); @@ -278,11 +270,17 @@ contract RaidGeld is ERC20, Ownable, Constants { } function player_dies(address player) private { - reset_player(player); + resetPlayer(player); players[player].has_active_session = false; _burn(msg.sender, balanceOf(player)); } + function setDaoTokenBuyInAmount(uint256 newAmount) external onlyOwner { + require(newAmount > 0, "Amount must be greater than 0"); + emit DaoTokenBuyInAmountSet(msg.sender, BUY_IN_DAO_TOKEN_AMOUNT, newAmount); + BUY_IN_DAO_TOKEN_AMOUNT = newAmount; + } + receive() external payable { revert("No plain Ether accepted, use register() function to check in :)"); } diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index 6f51181..b680008 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -22,6 +22,8 @@ library RaidGeldUtils { uint256 constant RISK_BETA = 15e17; + uint256 constant NUMBER_OF_BOSSES = 7; + function getBossPower(uint8 level) internal pure returns (uint256 power) { require(level <= 7, "Bosses only go to level 7"); if (level == 0) return 9000000; @@ -31,7 +33,6 @@ library RaidGeldUtils { else if (level == 4) return 90000000000; else if (level == 5) return 900000000000; else if (level == 6) return 9000000000000; - else return 0; } function getBossCumulativeChance(uint8 level) internal pure returns (uint256 chance) { @@ -102,11 +103,11 @@ library RaidGeldUtils { } // Calculates whether user survives the fight - function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 prevrandao) internal pure returns (bool) { + function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 _randomSeed) internal pure returns (bool) { uint256 bossPower = getBossPower(bossLevel); uint256 bossRoll = getBossChance(bossLevel); require(geldBurnt <= bossPower, "Cant try to defeat boss with more than what boss power is"); - uint256 random_n = random(prevrandao, 1, 100); + uint256 random_n = random(_randomSeed, 1, 100); // Relative power as in, you can only put in 800 geld to defeat 900 geld boss, // but you will get exponentially worse chances uint256 relativePower = ((geldBurnt ** 2) * 100) / bossPower ** 2; @@ -114,25 +115,25 @@ library RaidGeldUtils { return roll < bossRoll; } - function generate_boss_variants(uint256 prevrandao) internal pure returns (uint8[7] memory boss_variants) { + function generate_boss_variants(uint256 _randomSeed) internal pure returns (uint8[7] memory boss_variants) { // We shuffle the possible variants so each run is a bit different - uint8[6] memory array = [0, 1, 2, 3, 4, 5]; - for (uint256 i = array.length - 1; i > 0; i--) { + uint8[NUMBER_OF_BOSSES] memory array = [0, 1, 2, 3, 4, 5, 6]; + for (uint256 i = NUMBER_OF_BOSSES - 2; i > 0; i--) { // generate a pseudo-random index based on the prevrandao - uint256 j = uint256(keccak256(abi.encodePacked(prevrandao, i))) % (i + 1); + uint256 j = uint256(keccak256(abi.encodePacked(_randomSeed, i))) % (i + 1); // swap elements at i and j (array[i], array[j]) = (array[j], array[i]); } // Last boss is always the same old super Moloch - return [array[0], array[1], array[2], array[3], array[4], array[5], 6]; + return array; } // TODO: Implement actual randomness - function random(uint256 prevrandao, uint256 min, uint256 max) internal pure returns (uint256) { + function random(uint256 _randomSeed, uint256 min, uint256 max) internal pure returns (uint256) { // returns 0 - 100 require(max > min, "Max must be greater than min"); require(max < type(uint256).max, "Dont use largest possible uint"); uint256 range = max - min + 1; - return min + (uint256(keccak256(abi.encodePacked(prevrandao))) % range); + return min + (uint256(keccak256(abi.encodePacked(_randomSeed))) % range); } } From 8d845e989090ff5fb7266102143c5a521fa3c587 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Thu, 31 Oct 2024 13:26:02 +0100 Subject: [PATCH 12/19] Fixes wrong boss rewards calculation --- src/RaidGeldUtils.sol | 4 ++-- test/RaidGeldUtils.t.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index b680008..b327489 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -98,8 +98,8 @@ library RaidGeldUtils { function calculateBossReward(uint8 bossLevel, uint256 baseReward) internal pure returns (uint256) { // 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); + uint256 rewardMultiplier = ((2 * (1e18 - cumulativeChance)) ** 2); + return (baseReward * rewardMultiplier) / 1e18 ** 2; } // Calculates whether user survives the fight diff --git a/test/RaidGeldUtils.t.sol b/test/RaidGeldUtils.t.sol index 2859ab1..d5997e0 100644 --- a/test/RaidGeldUtils.t.sol +++ b/test/RaidGeldUtils.t.sol @@ -142,7 +142,7 @@ contract raid_geldTest is Test { function test_4_print_boss_rewards() public { uint256 total = 0; for (uint8 i = 0; i < 7; i++) { - uint256 reward = RaidGeldUtils.calculateBossReward(i, 500); + uint256 reward = RaidGeldUtils.calculateBossReward(i, 500e18); console.log("Reward", i,reward); total += reward; } From 9f6d94c8f79ccd8a556db6684286dd0d7a5204e4 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Thu, 31 Oct 2024 13:50:35 +0100 Subject: [PATCH 13/19] Added test for last boss uniqueness --- src/RaidGeld.sol | 5 ++--- test/RaidGeldUtils.t.sol | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 1da2f84..7afff11 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -28,7 +28,7 @@ contract RaidGeld is ERC20, Ownable, Constants { // Events event PlayerRegistered(address indexed player, uint256 initialGeld); - event PleyerStrikesAgain(address indexed player, uint256 totalRewards, uint256 initialGeld); + event PlayerStrikesAgain(address indexed player, uint256 totalRewards, uint256 initialGeld); event RaidPerformed(address indexed player, uint256 totalMinted, uint256 geldBalance); event UnitAdded( address indexed player, @@ -80,8 +80,7 @@ contract RaidGeld is ERC20, Ownable, Constants { players[player].n_runs += 1; if (existing_player) { - // TODO: Emit new run - emit PleyerStrikesAgain(player, players[player].total_rewards, INITIAL_GELD); + emit PlayerStrikesAgain(player, players[player].total_rewards, INITIAL_GELD); } else { // Emit event emit PlayerRegistered(msg.sender, INITIAL_GELD); diff --git a/test/RaidGeldUtils.t.sol b/test/RaidGeldUtils.t.sol index d5997e0..5bb42b3 100644 --- a/test/RaidGeldUtils.t.sol +++ b/test/RaidGeldUtils.t.sol @@ -148,4 +148,9 @@ contract raid_geldTest is Test { } console.log("Total", total); } + + function test_5_last_boss_is_unique(uint256 _randomSeed) public { + uint8[7] memory bosses = RaidGeldUtils.generate_boss_variants(_randomSeed); + vm.assertEq(bosses[6], 6); + } } From 5e9e4309700d064a6ecb883186e6b5c2fe465905 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Thu, 31 Oct 2024 13:53:39 +0100 Subject: [PATCH 14/19] Made bosses easier to fight --- app/src/components/BossInfo.tsx | 14 +++++++------- src/RaidGeldUtils.sol | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/components/BossInfo.tsx b/app/src/components/BossInfo.tsx index eb36b23..a771a27 100644 --- a/app/src/components/BossInfo.tsx +++ b/app/src/components/BossInfo.tsx @@ -46,13 +46,13 @@ const bossToChance: Record = { } const bossToBossPower: Record = { - 0: BigInt("9000000"), - 1: BigInt("90000000"), - 2: BigInt("900000000"), - 3: BigInt("9000000000"), - 4: BigInt("90000000000"), - 5: BigInt("900000000000"), - 6: BigInt("9000000000000"), + 0: BigInt("3000000"), + 1: BigInt("30000000"), + 2: BigInt("300000000"), + 3: BigInt("3000000000"), + 4: BigInt("30000000000"), + 5: BigInt("300000000000"), + 6: BigInt("3000000000000"), } const getBossChanceToDefeat = (bossLevel: BossLevel, geld_balance: bigint) => { diff --git a/src/RaidGeldUtils.sol b/src/RaidGeldUtils.sol index b327489..f167959 100644 --- a/src/RaidGeldUtils.sol +++ b/src/RaidGeldUtils.sol @@ -26,13 +26,13 @@ library RaidGeldUtils { function getBossPower(uint8 level) internal pure returns (uint256 power) { require(level <= 7, "Bosses only go to level 7"); - if (level == 0) return 9000000; - else if (level == 1) return 90000000; - else if (level == 2) return 900000000; - else if (level == 3) return 9000000000; - else if (level == 4) return 90000000000; - else if (level == 5) return 900000000000; - else if (level == 6) return 9000000000000; + if (level == 0) return 3000000; + else if (level == 1) return 30000000; + else if (level == 2) return 300000000; + else if (level == 3) return 3000000000; + else if (level == 4) return 30000000000; + else if (level == 5) return 300000000000; + else if (level == 6) return 3000000000000; } function getBossCumulativeChance(uint8 level) internal pure returns (uint256 chance) { From 5608c5a8fa6d2fb5226410c0e95a1d64860dbb84 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Thu, 31 Oct 2024 13:57:05 +0100 Subject: [PATCH 15/19] Makes boss info show only if the user is registered + fixed mismatch on prices displayed in client --- app/src/components/RegistrationModal.tsx | 2 +- app/src/components/Scene.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/components/RegistrationModal.tsx b/app/src/components/RegistrationModal.tsx index 0161a9e..540d8ab 100644 --- a/app/src/components/RegistrationModal.tsx +++ b/app/src/components/RegistrationModal.tsx @@ -17,7 +17,7 @@ const RegistrationModal = ({ isOpen, setIsOpen }: RegistrationModalProps) => { return

Insert coins to continue

- +
diff --git a/app/src/components/Scene.tsx b/app/src/components/Scene.tsx index 997d97b..f7b8813 100644 --- a/app/src/components/Scene.tsx +++ b/app/src/components/Scene.tsx @@ -35,7 +35,7 @@ const Scene = () => {
- + {isRegistered && }
From ad0aa6e6d315a9ed58501b9540c5b44c9a812b4e Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Thu, 31 Oct 2024 14:09:18 +0100 Subject: [PATCH 16/19] Progression tier parsing BigInt by string rathere --- app/src/components/PixelatedQuote.tsx | 24 ++++++++++++------------ app/src/components/Scene.tsx | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/components/PixelatedQuote.tsx b/app/src/components/PixelatedQuote.tsx index 96ffd66..fc4f07a 100644 --- a/app/src/components/PixelatedQuote.tsx +++ b/app/src/components/PixelatedQuote.tsx @@ -4,17 +4,17 @@ import styles from "../styles/Army.module.css"; // Quote categories based on total minted GELD const PROGRESSION_TIERS = { - BEGINNER: BigInt(1000000), - NOVICE: BigInt(10000000), - INTERMEDIATE: BigInt(100000000), - EXPERIENCED: BigInt(1000000000), - EXPERT: BigInt(10000000000), - MASTER: BigInt(100000000000), - EPIC: BigInt(1000000000000), - LEGENDARY: BigInt(10000000000000), - GUILD_LEADER: BigInt(100000000000000), - DIVINE: BigInt(1000000000000000), - MAXIMUM: BigInt(10000000000000000), + BEGINNER: BigInt("1000000"), + NOVICE: BigInt("10000000"), + INTERMEDIATE: BigInt("100000000"), + EXPERIENCED: BigInt("1000000000"), + EXPERT: BigInt("10000000000"), + MASTER: BigInt("100000000000"), + EPIC: BigInt("1000000000000"), + LEGENDARY: BigInt("10000000000000"), + GUILD_LEADER: BigInt("100000000000000"), + DIVINE: BigInt("1000000000000000"), + MAXIMUM: BigInt("10000000000000000"), }; const quotes = { @@ -177,7 +177,7 @@ function PixelatedQuote() { intervalIdRef.current = setInterval(() => { const totalMinted = player?.total_minted ?? BigInt(0); - + // Show welcome message only once at the start if (!hasShownWelcome.current) { setCurrentQuote("Welcome to the Dark Forest!"); diff --git a/app/src/components/Scene.tsx b/app/src/components/Scene.tsx index ca19d1e..739eba3 100644 --- a/app/src/components/Scene.tsx +++ b/app/src/components/Scene.tsx @@ -20,7 +20,7 @@ const bossToMountainsClass = { } const Scene = () => { - const { isRegistered, boss } = usePlayer(); + const { isRegistered, boss, player } = usePlayer(); const [isLeaderboardOpen, setIsLeaderboardOpen] = useState(false); const handleMusicReady = useCallback((unmute: () => void) => { @@ -38,7 +38,7 @@ const Scene = () => {
- {isRegistered && } + {isRegistered && player?.has_active_session && }
From 61ab8f21478dcabea47e850fe0518bda977369d5 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Thu, 31 Oct 2024 14:24:30 +0100 Subject: [PATCH 17/19] Changed header for dead players --- app/src/components/Header.tsx | 29 +++++++++++++++-------------- app/src/styles/Header.module.css | 3 +++ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/src/components/Header.tsx b/app/src/components/Header.tsx index 60eecdf..acf55c9 100644 --- a/app/src/components/Header.tsx +++ b/app/src/components/Header.tsx @@ -9,37 +9,38 @@ import { useModal } from "../providers/ModalProvider"; const Header = () => { const { isConnected } = useAccount(); - const { isRegistered, army } = usePlayer(); + const { isRegistered, player, army } = usePlayer(); const { openRegistrationModal } = useModal(); const title = useMemo(() => { - return isRegistered ? `SLAY THE MOLOCH` : - !isConnected ? "Connect your wallet traveler ☝️ and then ..." : - "Click here to start 😈" - }, [isConnected, isRegistered]) + return isRegistered && !player?.has_active_session ? `You died 😇 Click here to start again and ...` : + isRegistered ? `SLAY THE MOLOCH` : + !isConnected ? "Connect your wallet traveler ☝️ and then ..." : + "Click here to start 😈" + }, [isConnected, isRegistered, player?.has_active_session]) const subtitle = useMemo(() => { - if (isRegistered) { + if (isRegistered && player?.has_active_session) { return } else { - return

SLAY THE MOLOCH

+ return

SLAY THE MOLOCH

} - }, [isRegistered]) + }, [isRegistered, player?.has_active_session, isConnected]) const perSecondParagraph = useMemo(() => { const perSecond = toReadable(army?.profit_per_second ?? BigInt(0)) - return (isRegistered) ? + return (isRegistered && player?.has_active_session) ?

per second: {perSecond}

: null - }, [isRegistered, army?.profit_per_second]) + }, [isRegistered, army?.profit_per_second, player?.has_active_session]) const onRegister = useCallback(() => { - if (isRegistered) return + if (player?.has_active_session) return openRegistrationModal() - }, [isRegistered, openRegistrationModal]) + }, [player?.has_active_session, openRegistrationModal]) - return
-

{title}

+ return
+

{title}

{subtitle} {perSecondParagraph}
diff --git a/app/src/styles/Header.module.css b/app/src/styles/Header.module.css index ce97767..9f56541 100644 --- a/app/src/styles/Header.module.css +++ b/app/src/styles/Header.module.css @@ -2,6 +2,9 @@ position: relative; margin-top: 5rem; z-index: 1; + &.clickable { + cursor: pointer; + } } .title { font-size: 1.5rem; From 9443561b9c2707695503b2868033df8eaed23196 Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Thu, 31 Oct 2024 14:29:39 +0100 Subject: [PATCH 18/19] Formatting for Boss info display --- app/src/components/BossInfo.tsx | 4 ++-- app/src/components/WaitingForTxModal.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/components/BossInfo.tsx b/app/src/components/BossInfo.tsx index a771a27..eede405 100644 --- a/app/src/components/BossInfo.tsx +++ b/app/src/components/BossInfo.tsx @@ -81,9 +81,9 @@ const BossInfo = () => { }, [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

+

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

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

diff --git a/app/src/components/WaitingForTxModal.tsx b/app/src/components/WaitingForTxModal.tsx index be8ccde..ce190c4 100644 --- a/app/src/components/WaitingForTxModal.tsx +++ b/app/src/components/WaitingForTxModal.tsx @@ -24,7 +24,7 @@ const WaitingForTxModal = ({
-

Writing contract ...

+

Spinning the chain ...

} From 5b99997170596966ce12e0d4b680af23f0a900ac Mon Sep 17 00:00:00 2001 From: Mitja Belak Date: Thu, 31 Oct 2024 14:51:47 +0100 Subject: [PATCH 19/19] Added Boss Defeated and Prestige events --- app/src/providers/PlayerProvider.tsx | 6 +++++- src/RaidGeld.sol | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/providers/PlayerProvider.tsx b/app/src/providers/PlayerProvider.tsx index 063bc35..6941033 100644 --- a/app/src/providers/PlayerProvider.tsx +++ b/app/src/providers/PlayerProvider.tsx @@ -169,8 +169,12 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => { abi, address: contractAddress, functionName: 'raid', + }, { + onSuccess: (hash) => { + setHashAndCallback([hash, resetHashAndCallback]) + } }) - }, [writeContract]) + }, [writeContract, resetHashAndCallback]) const addUnit = useCallback((unit: UnitType) => { writeContract({ diff --git a/src/RaidGeld.sol b/src/RaidGeld.sol index 7afff11..6f69b2e 100644 --- a/src/RaidGeld.sol +++ b/src/RaidGeld.sol @@ -42,6 +42,8 @@ contract RaidGeld is ERC20, Ownable, Constants { uint16 championLevel ); event DaoTokenBuyInAmountSet(address indexed owner, uint256 oldAmount, uint256 newAmount); + event PrestigeGained(address indexed player, uint32 prestigeLevel); + event BossDefeated(address indexed player, uint8 bossLevel, uint256 earnings); // Modifier for functions that should only be available to registered players modifier onlyPlayer() { @@ -252,9 +254,12 @@ contract RaidGeld is ERC20, Ownable, Constants { uint256 reward = RaidGeldUtils.calculateBossReward(boss_to_attack.level, BUY_IN_DAO_TOKEN_AMOUNT); players[msg.sender].total_rewards += reward; daoToken.transfer(msg.sender, reward); + emit BossDefeated(msg.sender, boss_to_attack.level, reward); + if (boss_to_attack.level == 6) { // User ascends! Moloch is defeated, user can start a new run players[msg.sender].prestige_level += 1; + emit PrestigeGained(msg.sender, players[msg.sender].prestige_level); player_dies(msg.sender); return [hasWonBattle, true /* New prestige level! */ ]; } else {