From ba29bcd55e14dab33644b8a218bf8cafac755760 Mon Sep 17 00:00:00 2001 From: LidamaoHub Date: Sat, 28 Oct 2023 13:54:24 +0800 Subject: [PATCH] =?UTF-8?q?Update:=E6=96=B0=E5=A2=9Eloot=E5=90=88=E7=BA=A6?= =?UTF-8?q?=E4=B8=8Euser=E5=90=88=E7=BA=A6,=E5=B9=B6=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contracts/script/GlobalConfigInit.sol | 10 +- packages/contracts/script/PostDeploy.s.sol | 6 +- packages/contracts/src/other/Base64.sol | 62 +++++ packages/contracts/src/other/Loot.sol | 253 ++++++++++++++++++ packages/contracts/src/other/LootSuit.sol | 238 ++++++++++++++++ packages/contracts/src/other/MRandom.sol | 62 +++++ packages/contracts/src/other/User.sol | 197 ++++++++++++++ packages/contracts/worlds.json | 2 +- 8 files changed, 825 insertions(+), 5 deletions(-) create mode 100644 packages/contracts/src/other/Base64.sol create mode 100644 packages/contracts/src/other/Loot.sol create mode 100644 packages/contracts/src/other/LootSuit.sol create mode 100644 packages/contracts/src/other/MRandom.sol create mode 100644 packages/contracts/src/other/User.sol diff --git a/packages/contracts/script/GlobalConfigInit.sol b/packages/contracts/script/GlobalConfigInit.sol index 3aa3ec8b..c4a86aea 100644 --- a/packages/contracts/script/GlobalConfigInit.sol +++ b/packages/contracts/script/GlobalConfigInit.sol @@ -7,12 +7,16 @@ import {GlobalConfig} from "../src/codegen/Tables.sol"; import { GLOBAL_CONFIG_KEY } from "../src/Constants.sol"; library GlobalConfigInit { - function initGlobalConfig(IWorld _world) internal { - address userContract = 0x5FbDB2315678afecb367f032d93F642f64180aa3; + function initGlobalConfig(IWorld _world,address _userContract,address _lootContract) internal { GlobalConfig.setUserContract( _world, GLOBAL_CONFIG_KEY, //key - userContract + _userContract + ); + GlobalConfig.setLootContract( + _world, + GLOBAL_CONFIG_KEY, //key + _lootContract ); } } diff --git a/packages/contracts/script/PostDeploy.s.sol b/packages/contracts/script/PostDeploy.s.sol index 7c61b6dd..500a0349 100644 --- a/packages/contracts/script/PostDeploy.s.sol +++ b/packages/contracts/script/PostDeploy.s.sol @@ -8,6 +8,8 @@ import { GameConfigInit } from "./GameConfigInit.sol"; import { BattleConfigInit } from "./BattleConfigInit.sol"; import { GlobalConfigInit } from "./GlobalConfigInit.sol"; import { console } from "forge-std/console.sol"; +import "../src/other/User.sol"; +import "../src/other/Loot.sol"; contract PostDeploy is Script { function run(address worldAddress) external { @@ -18,11 +20,13 @@ contract PostDeploy is Script { vm.startBroadcast(deployerPrivateKey); console.log(" ========== PostDeploy ========== "); + MUser muser = new MUser(2, "MUser", "MUser", "", ""); + MLoot mloot = new MLoot("", "MLOOT", "MLOOT", "", 2); // ------------------ INIT ------------------ GameConfigInit.initGameConfig(IWorld(worldAddress)); BattleConfigInit.initBattleConfig(IWorld(worldAddress)); - GlobalConfigInit.initGlobalConfig(IWorld(worldAddress)); + GlobalConfigInit.initGlobalConfig(IWorld(worldAddress), address(muser),address(mloot)); vm.stopBroadcast(); diff --git a/packages/contracts/src/other/Base64.sol b/packages/contracts/src/other/Base64.sol new file mode 100644 index 00000000..685083b3 --- /dev/null +++ b/packages/contracts/src/other/Base64.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; +/// [MIT License] +/// @title Base64 +/// @notice Provides a function for encoding some bytes in base64 +/// @author Brecht Devos +library Base64 { + bytes internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /// @notice Encodes some bytes to the base64 representation + function encode(bytes memory data) internal pure returns (string memory) { + uint256 len = data.length; + if (len == 0) return ""; + + // multiply by 4/3 rounded up + uint256 encodedLen = 4 * ((len + 2) / 3); + + // Add some extra buffer at the end + bytes memory result = new bytes(encodedLen + 32); + + bytes memory table = TABLE; + + assembly { + let tablePtr := add(table, 1) + let resultPtr := add(result, 32) + + for { + let i := 0 + } lt(i, len) { + + } { + i := add(i, 3) + let input := and(mload(add(data, i)), 0xffffff) + + let out := mload(add(tablePtr, and(shr(18, input), 0x3F))) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF)) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF)) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF)) + out := shl(224, out) + + mstore(resultPtr, out) + + resultPtr := add(resultPtr, 4) + } + + switch mod(len, 3) + case 1 { + mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) + } + case 2 { + mstore(sub(resultPtr, 1), shl(248, 0x3d)) + } + + mstore(result, encodedLen) + } + + return string(result); + } +} \ No newline at end of file diff --git a/packages/contracts/src/other/Loot.sol b/packages/contracts/src/other/Loot.sol new file mode 100644 index 00000000..edde3429 --- /dev/null +++ b/packages/contracts/src/other/Loot.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; +import "./LootSuit.sol"; +import "./Base64.sol"; +import './MRandom.sol'; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract MLoot is Suit, ERC721,MRandom { + using Strings for uint256; + + constructor( + string memory _desc, + string memory symbol, + string memory name, + string memory _notRevealedInfo, + uint256 _waitBlockCount + ) ERC721(symbol, name) { + desc = _desc; + owner = msg.sender; + waitBlockCount = _waitBlockCount; + notRevealedInfo = _notRevealedInfo; + } + + + + + + struct Loot { + uint256 randomId; + address owner; + string Weapon; + string Chest; + string Head; + string Waist; + string Foot; + string Hand; + string Neck; + string Ring; + RandomState state; + } + + uint256 public tokenId; + uint256 public waitBlockCount; + + + address owner; + string desc; + string notRevealedInfo; + + mapping(uint256 => Loot) public lootList; + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + + function tokenURI( + uint256 _tokenId + ) public view override returns (string memory) { + string[17] memory parts; + Loot memory loot = lootList[_tokenId]; + if(loot.state == RandomState.Pending){ + string memory r = string( + abi.encodePacked( + "data:application/json;base64,", + notRevealedInfo + ) + ); + return r; + } + require(loot.state != RandomState.Inited, "Box not existed"); + + parts[ + 0 + ] = ''; + + parts[1] = loot.Weapon; + + parts[2] = ''; + + parts[3] = loot.Chest; + + parts[4] = ''; + + parts[5] = loot.Head; + + parts[6] = ''; + + parts[7] = loot.Waist; + + parts[8] = ''; + + parts[9] = loot.Foot; + + parts[10] = ''; + + parts[11] = loot.Hand; + + parts[12] = ''; + + parts[13] = loot.Neck; + + parts[14] = ''; + + parts[15] = loot.Ring; + + parts[16] = ""; + + string memory output = string( + abi.encodePacked( + parts[0], + parts[1], + parts[2], + parts[3], + parts[4], + parts[5], + parts[6], + parts[7], + parts[8] + ) + ); + output = string( + abi.encodePacked( + output, + parts[9], + parts[10], + parts[11], + parts[12], + parts[13], + parts[14], + parts[15], + parts[16] + ) + ); + + string memory json = Base64.encode( + bytes( + string( + abi.encodePacked( + '{"name": "MLoot #', + _tokenId.toString(), + '", "description":"', + desc, + '","image": "data:image/svg+xml;base64,', + Base64.encode(bytes(output)), + '"}' + ) + ) + ) + ); + output = string( + abi.encodePacked("data:application/json;base64,", json) + ); + + return output; + } + + function luck( + uint8 rand, + string[] memory sourceArray + ) internal view returns (string memory) { + string memory output = sourceArray[rand % sourceArray.length]; + + uint256 greatness = rand % 21; + if (greatness > 14) { + output = string( + abi.encodePacked(output, " ", suffixes[rand % suffixes.length]) + ); + } + if (greatness >= 19) { + string[2] memory name; + name[0] = namePrefixes[rand % namePrefixes.length]; + name[1] = nameSuffixes[rand % nameSuffixes.length]; + if (greatness == 19) { + output = string( + abi.encodePacked('"', name[0], " ", name[1], '" ', output) + ); + } else { + output = string( + abi.encodePacked( + '"', + name[0], + " ", + name[1], + '" ', + output, + " +1" + ) + ); + } + } + return output; + } + + function revealNFT(uint256 _tokenId) external { + Loot storage loot = lootList[_tokenId]; + require(loot.owner == msg.sender, "only owner can reveal the box"); + uint8[] memory random_numbers = getRandom(loot.randomId, 8,waitBlockCount); + loot.Weapon = luck(random_numbers[0], weapons); + loot.Chest = luck(random_numbers[1], chestArmor); + loot.Head = luck(random_numbers[2], headArmor); + loot.Waist = luck(random_numbers[3], waistArmor); + loot.Foot = luck(random_numbers[4], footArmor); + loot.Hand = luck(random_numbers[5], handArmor); + loot.Neck = luck(random_numbers[6], necklaces); + loot.Ring = luck(random_numbers[7], rings); + loot.state = RandomState.Confirmed; + } + + function mint() external { + // init loot box + Loot storage loot = lootList[tokenId]; + loot.owner = msg.sender; + loot.state = RandomState.Pending; + loot.randomId = randomId; + requestRandom(randomId); + _mint(msg.sender, tokenId); + tokenId++; + randomId++; + } + + + + + function withdraw(address to) public onlyOwner { + uint256 balance = address(this).balance; + require(balance > 0, "sufficient funds"); + payable(to).transfer(balance); + } + + function withdrawErc20( + address _targetAddress, + address _contractAddress, + uint256 _amount + ) external onlyOwner { + IERC20(_contractAddress).transfer(_targetAddress, _amount); + } + function getStructInfo(uint256 _tokenId) external view returns(string memory,string memory,string memory,string memory,string memory,string memory,string memory,string memory){ + Loot memory loot = lootList[_tokenId]; + require(loot.state == RandomState.Confirmed,"User not exists"); + return(loot.Weapon, + loot.Chest, + loot.Head, + loot.Waist, + loot.Foot, + loot.Hand, + loot.Neck, + loot.Ring); + } +} diff --git a/packages/contracts/src/other/LootSuit.sol b/packages/contracts/src/other/LootSuit.sol new file mode 100644 index 00000000..28356455 --- /dev/null +++ b/packages/contracts/src/other/LootSuit.sol @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +contract Suit { + + string[] internal weapons = [ + "Warhammer", + "Quarterstaff", + "Maul", + "Mace", + "Club", + "Katana", + "Falchion", + "Scimitar", + "Long Sword", + "Short Sword", + "Ghost Wand", + "Grave Wand", + "Bone Wand", + "Wand", + "Grimoire", + "Chronicle", + "Tome", + "Book" + ]; + + string[] internal chestArmor = [ + "Divine Robe", + "Silk Robe", + "Linen Robe", + "Robe", + "Shirt", + "Demon Husk", + "Dragonskin Armor", + "Studded Leather Armor", + "Hard Leather Armor", + "Leather Armor", + "Holy Chestplate", + "Ornate Chestplate", + "Plate Mail", + "Chain Mail", + "Ring Mail" + ]; + + string[] internal headArmor = [ + "Ancient Helm", + "Ornate Helm", + "Great Helm", + "Full Helm", + "Helm", + "Demon Crown", + "Dragon's Crown", + "War Cap", + "Leather Cap", + "Cap", + "Crown", + "Divine Hood", + "Silk Hood", + "Linen Hood", + "Hood" + ]; + + string[] internal waistArmor = [ + "Ornate Belt", + "War Belt", + "Plated Belt", + "Mesh Belt", + "Heavy Belt", + "Demonhide Belt", + "Dragonskin Belt", + "Studded Leather Belt", + "Hard Leather Belt", + "Leather Belt", + "Brightsilk Sash", + "Silk Sash", + "Wool Sash", + "Linen Sash", + "Sash" + ]; + + string[] internal footArmor = [ + "Holy Greaves", + "Ornate Greaves", + "Greaves", + "Chain Boots", + "Heavy Boots", + "Demonhide Boots", + "Dragonskin Boots", + "Studded Leather Boots", + "Hard Leather Boots", + "Leather Boots", + "Divine Slippers", + "Silk Slippers", + "Wool Shoes", + "Linen Shoes", + "Shoes" + ]; + + string[] internal handArmor = [ + "Holy Gauntlets", + "Ornate Gauntlets", + "Gauntlets", + "Chain Gloves", + "Heavy Gloves", + "Demon's Hands", + "Dragonskin Gloves", + "Studded Leather Gloves", + "Hard Leather Gloves", + "Leather Gloves", + "Divine Gloves", + "Silk Gloves", + "Wool Gloves", + "Linen Gloves", + "Gloves" + ]; + + string[] internal necklaces = ["Necklace", "Amulet", "Pendant"]; + + string[] internal rings = [ + "Gold Ring", + "Silver Ring", + "Bronze Ring", + "Platinum Ring", + "Titanium Ring" + ]; + + string[] internal suffixes = [ + "of Power", + "of Giants", + "of Titans", + "of Skill", + "of Perfection", + "of Brilliance", + "of Enlightenment", + "of Protection", + "of Anger", + "of Rage", + "of Fury", + "of Vitriol", + "of the Fox", + "of Detection", + "of Reflection", + "of the Twins" + ]; + + string[] internal namePrefixes = [ + "Agony", + "Apocalypse", + "Armageddon", + "Beast", + "Behemoth", + "Blight", + "Blood", + "Bramble", + "Brimstone", + "Brood", + "Carrion", + "Cataclysm", + "Chimeric", + "Corpse", + "Corruption", + "Damnation", + "Death", + "Demon", + "Dire", + "Dragon", + "Dread", + "Doom", + "Dusk", + "Eagle", + "Empyrean", + "Fate", + "Foe", + "Gale", + "Ghoul", + "Gloom", + "Glyph", + "Golem", + "Grim", + "Hate", + "Havoc", + "Honour", + "Horror", + "Hypnotic", + "Kraken", + "Loath", + "Maelstrom", + "Mind", + "Miracle", + "Morbid", + "Oblivion", + "Onslaught", + "Pain", + "Pandemonium", + "Phoenix", + "Plague", + "Rage", + "Rapture", + "Rune", + "Skull", + "Sol", + "Soul", + "Sorrow", + "Spirit", + "Storm", + "Tempest", + "Torment", + "Vengeance", + "Victory", + "Viper", + "Vortex", + "Woe", + "Wrath", + "Light's", + "Shimmering" + ]; + + string[] internal nameSuffixes = [ + "Bane", + "Root", + "Bite", + "Song", + "Roar", + "Grasp", + "Instrument", + "Glow", + "Bender", + "Shadow", + "Whisper", + "Shout", + "Growl", + "Tear", + "Peak", + "Form", + "Sun", + "Moon" + ]; +} \ No newline at end of file diff --git a/packages/contracts/src/other/MRandom.sol b/packages/contracts/src/other/MRandom.sol new file mode 100644 index 00000000..a76fac7b --- /dev/null +++ b/packages/contracts/src/other/MRandom.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +contract MRandom { + enum RandomState { + Inited, + Pending, + Confirmed + } + struct Random { + uint256 blockNumber; + address author; + } + uint256 public randomId; + mapping(uint256 => Random) public randomList; + + + event NewRandom(uint256 randomId, address author); + + function requestRandom(uint256 _randomId) internal { + Random storage r = randomList[_randomId]; + r.author = msg.sender; + r.blockNumber = block.number; + emit NewRandom(randomId, msg.sender); + } + + function getRandom( + uint256 _randomId, + uint256 _count, + uint256 _waitBlockCount + ) internal view returns (uint8[] memory) { + require(_randomId < randomId, "random does not exists"); + Random memory r = randomList[_randomId]; + + require(msg.sender == r.author, "only random creator can get random"); + uint8[] memory randomNumberList = new uint8[](_count); + require( + block.number >= r.blockNumber + _waitBlockCount, + "too early to get random seed" + ); + uint256 seed = uint256(blockhash(r.blockNumber + 2)); + // 一次处理一个uint256随机数 + uint256 randomNumber = uint256(keccak256(abi.encodePacked(seed))); + + // 截断后存入属性数组 + for (uint8 i = 0; i < _count; i++) { + uint8 digit = uint8(randomNumber % 100); + randomNumberList[i] = digit; + randomNumber = randomNumber / 100; + } + return randomNumberList; + } + + function choice( + uint8 rand, + string[] memory sourceArray + ) internal pure returns (string memory) { + string memory output = sourceArray[rand % sourceArray.length]; + + return output; + } +} \ No newline at end of file diff --git a/packages/contracts/src/other/User.sol b/packages/contracts/src/other/User.sol new file mode 100644 index 00000000..94d6ed64 --- /dev/null +++ b/packages/contracts/src/other/User.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "./MRandom.sol"; +import "./Base64.sol"; + +contract MUser is ERC721, MRandom { + using Strings for uint256; + + struct User { + uint256 randomId; + address owner; + uint256 HP; + uint256 Attack; + uint256 AttackRange; + uint256 Speed; + uint256 Strength; + uint256 Space; + RandomState state; + } + + uint256 public tokenId; + uint256 public waitBlockCount; + address public owner; + string notRevealedInfo; + string revealedDesc; + + constructor( + uint256 _waitBlockCount, + string memory _symbol, + string memory _name, + string memory _notRevealedInfo, + string memory _revealedDesc + ) ERC721(_symbol, _name) { + owner = msg.sender; + waitBlockCount = _waitBlockCount; + notRevealedInfo = _notRevealedInfo; + revealedDesc = _revealedDesc; + } + + mapping(uint256 => User) public userList; + + function mint() external { + // init loot box + User storage user = userList[tokenId]; + user.owner = msg.sender; + user.state = RandomState.Pending; + user.randomId = randomId; + requestRandom(randomId); + _mint(msg.sender, tokenId); + tokenId++; + randomId++; + } + + function revealNFT(uint256 _tokenId) external { + User storage user = userList[_tokenId]; + require(user.owner == msg.sender, "only owner can reveal the box"); + uint8[] memory random_numbers = getRandom( + user.randomId, + 8, + waitBlockCount + ); + + user.HP = getRange(random_numbers[0], 100, 10, 5); + user.Attack = getRange(random_numbers[1], 30, 10, 2); + user.AttackRange = getRange(random_numbers[2], 2, 50, 1); + user.Speed = getRange(random_numbers[3], 3, 50, 2); + user.Strength = getRange(random_numbers[4], 20, 10, 3); + user.Space = getRange(random_numbers[5], 2, 50, 1); + + user.state = RandomState.Confirmed; + } + + function getRange( + uint8 _rand, + uint256 _start, + uint256 _step, + uint256 _stepLength + ) internal pure returns (uint256) { + uint256 times = uint256((_rand + 1) / _step); + return _start + times * _stepLength; + } + + function concat( + string memory _key, + string memory _value + ) internal pure returns (string memory) { + return string(abi.encodePacked(_key, " : ", _value)); + } + + function createSVG(User memory user) internal pure returns (string memory) { + string[13] memory parts; + + parts[ + 0 + ] = ''; + + parts[1] = concat("HP", user.HP.toString()); + + parts[2] = ''; + + parts[3] = concat("Attack", user.Attack.toString()); + + parts[4] = ''; + + parts[5] = concat("AttackRange", user.AttackRange.toString()); + + parts[6] = ''; + + parts[7] = concat("Speed",user.Speed.toString()); + + parts[8] = ''; + + parts[9] = concat("Strength", user.Strength.toString()); + + parts[10] = ''; + + parts[11] = concat("Space", user.Space.toString()); + + parts[12] = ""; + + string memory output ; + for(uint256 i;i