diff --git a/src/implementations/ExperienceAndItemsImplementation.sol b/src/implementations/ExperienceAndItemsImplementation.sol index 5663e31..e154efa 100644 --- a/src/implementations/ExperienceAndItemsImplementation.sol +++ b/src/implementations/ExperienceAndItemsImplementation.sol @@ -325,17 +325,22 @@ contract ExperienceAndItemsImplementation is ERC1155Holder, Initializable, ERC11 totalExperience += _amount; return totalExperience; } - + /** + * adds a new required item to the array of requirments in the item type + * @param itemId the itemId of the item type to be modified + * @param requiredItemId the itemId of the item to be added to the requirements array + * @param amount the amount of the required item to be required + */ function addItemRequirement(uint256 itemId, uint256 requiredItemId, uint256 amount) public onlyDungeonMaster - returns (bool) + returns (bool success) { Item memory modifiedItem = items[itemId]; - uint256 newRequiredItemId = items[requiredItemId].tokenId; bool duplicate; + for (uint256 i = 0; i < modifiedItem.requirements.length; i++) { - if (modifiedItem.requirements[i][0] == newRequiredItemId) { + if (modifiedItem.requirements[i][0] == requiredItemId) { duplicate = true; } } @@ -343,14 +348,45 @@ contract ExperienceAndItemsImplementation is ERC1155Holder, Initializable, ERC11 require(!duplicate, "Cannot add a requirement that has already been added"); uint256[] memory newRequirement = new uint256[](2); - newRequirement[0] = newRequiredItemId; + newRequirement[0] = requiredItemId; newRequirement[1] = amount; items[itemId].requirements.push(newRequirement); - return true; + success = true; + + return success; } - function removeItemRequirement(uint256 itemId, uint256 removedItemId) public onlyDungeonMaster returns (bool) {} + /** + * + * @param itemId the itemId of the item type to be modified + * @param removedItemId the itemId of the requirement that is to be removed. + * so if the item requires 2 of itemId 1 to be burnt in order to claim the item then you put in 1 + * and it will remove the requirment with itemId 1 + */ + function removeItemRequirement(uint256 itemId, uint256 removedItemId) public onlyDungeonMaster returns (bool) { + uint256[][] memory arr = items[itemId].requirements; + bool success = false; + for(uint256 i; i < arr.length; i++){ + if(arr[i][0] == removedItemId){ + for (uint256 j = i; j < arr.length; j++) { + if (j + 1 < arr.length) { + arr[j] = arr[j + 1]; + } else if (j + 1 >= arr.length) { + arr[j] = new uint256[](2); + } + } + success = true; + } + } + + if(success == true){ + items[itemId].requirements = arr; + items[itemId].requirements.pop(); + } + + return success; + } /** * drops loot and/or exp after a completed quest items dropped through dropLoot do cost exp. @@ -381,7 +417,7 @@ contract ExperienceAndItemsImplementation is ERC1155Holder, Initializable, ERC11 * @param amount the amount of items to be sent to the player token */ - function _transferItem(address _to, uint256 itemId, uint256 amount) internal { + function _transferItem(address _to, uint256 itemId, uint256 amount) private { Item memory item = items[itemId]; require(characterSheets.hasRole(NPC, _to), "Can Only transfer Items to an NPC"); @@ -416,12 +452,13 @@ contract ExperienceAndItemsImplementation is ERC1155Holder, Initializable, ERC11 for (uint256 i; i < item.requirements.length; i++) { newRequirement = item.requirements[i]; + uint256 newTokenId = items[newRequirement[0]].tokenId; require( - balanceOf(NFTAddress, newRequirement[0]) >= newRequirement[1] * amount, "Not enough required item." + balanceOf(NFTAddress, newTokenId) >= newRequirement[1] * amount, "Not enough required item." ); - _balanceOf[NFTAddress][newRequirement[0]] -= newRequirement[1] * amount; + _balanceOf[NFTAddress][newTokenId] -= newRequirement[1] * amount; } _balanceOf[address(this)][item.tokenId] -= amount; diff --git a/src/lib/Structs.sol b/src/lib/Structs.sol index f54826d..012ebe3 100644 --- a/src/lib/Structs.sol +++ b/src/lib/Structs.sol @@ -7,7 +7,7 @@ struct Item { string name; /// @dev the name of this item uint256 supply; /// @dev the number of this item to be created. uint256 supplied; /// @dev the number of this item that have been given out or claimed - uint256[][] requirements; /// @dev an array of arrays that are two long containing the required erc1155 tokenId and the amount required eg. [[tokenId, amount], [tokenId, amount]] + uint256[][] requirements; /// @dev an array of arrays that are two long containing the required itemId and the amount required eg. [[itemId, amount], [itemId, amount]] bool soulbound; /// @dev is this item soulbound or not /// @dev claimable: if bytes32(0) then items are claimable by anyone, otherwise upload a merkle root diff --git a/test/ExperienceAndItems.t.sol b/test/ExperienceAndItems.t.sol index 650403e..0c1bddd 100644 --- a/test/ExperienceAndItems.t.sol +++ b/test/ExperienceAndItems.t.sol @@ -174,14 +174,12 @@ contract ExperienceAndItemsTest is Test, SetUp { } function testDropLootRevert() public { - vm.startPrank(admin); - bytes memory newItem = createNewItem("staff", false, bytes32(0)); + address player1NFT = characterSheets.getCharacterSheetByPlayerId( characterSheets.memberAddressToTokenId(player1) ).ERC6551TokenAddress; - - (, uint256 _itemId) = experience.createItemType(newItem); - vm.stopPrank(); + vm.prank(admin); + (, uint256 _itemId) = createNewItemType("staff"); address[] memory players = new address[](1); players[0] = player1NFT; uint256[] memory itemIds = new uint256[](3); @@ -199,9 +197,8 @@ contract ExperienceAndItemsTest is Test, SetUp { } function testClaimItem() public { - bytes memory newItem = createNewItem("staff", false, bytes32(0)); vm.prank(admin); - (uint256 _tokenId, uint256 _itemId) = experience.createItemType(newItem); + (uint256 _tokenId, uint256 _itemId) = createNewItemType("staff"); uint256 playerId = characterSheets.memberAddressToTokenId(player1); @@ -219,7 +216,7 @@ contract ExperienceAndItemsTest is Test, SetUp { (bytes32[] memory proof, bytes32 root) = generateMerkleRootAndProof(itemIds, claimers, amounts, 0); - dropExp(nftAddress, 100000); + dropExp(nftAddress, 1000); vm.prank(admin); experience.updateItemClaimable(_itemId, root); @@ -235,6 +232,8 @@ contract ExperienceAndItemsTest is Test, SetUp { experience.claimItems(itemIds2, amounts2, proofs); assertEq(experience.balanceOf(nftAddress, _tokenId), 1, "Balance not equal"); + + assertEq(experience.balanceOf(nftAddress, 0), 900); } function testFindItemByName() public { @@ -279,4 +278,40 @@ contract ExperienceAndItemsTest is Test, SetUp { "incorrect uri returned" ); } + + function testAddItemRequirement() public { + vm.prank(admin); + (, uint256 hatItemId)=createNewItemType("hat"); + + vm.prank(admin); + experience.addItemRequirement(1, hatItemId, 100); + + Item memory modifiedItem = experience.getItemById(1); + + assertEq(modifiedItem.requirements.length, 2, "Requirement not added"); + assertEq(experience.getItemById(1).requirements[1][0], hatItemId, "wrong Id in requirements array"); + } + + function testRemoveItemRequirement() public { + vm.prank(admin); + (, uint256 hatItemId) = createNewItemType("hat"); + + vm.prank(admin); + experience.addItemRequirement(1, hatItemId, 100); + + Item memory modifiedItem = experience.getItemById(1); + + assertEq(modifiedItem.requirements.length, 2, "Requirement not added"); + assertEq(experience.getItemById(1).requirements[1][0], hatItemId, "wrong Id in requirements array"); + + vm.prank(admin); + experience.removeItemRequirement(1, hatItemId); + + modifiedItem = experience.getItemById(1); + assertEq(modifiedItem.requirements.length, 1, "requirement not removed"); + assertEq(modifiedItem.requirements[0][0], 0, "wrong requirement removed"); + assertEq(modifiedItem.requirements[0][1], 100, "Incorrect remaining amount"); + } + + } diff --git a/test/helpers/SetUp.sol b/test/helpers/SetUp.sol index 3c2f025..0dbb75a 100644 --- a/test/helpers/SetUp.sol +++ b/test/helpers/SetUp.sol @@ -130,5 +130,9 @@ contract SetUp is Test { root = merkle.getRoot(leaves); } - + + function createNewItemType(string memory name) public returns(uint256 tokenId, uint256 itemId){ + bytes memory newItem = createNewItem(name, false, bytes32(0)); + (tokenId, itemId) = experience.createItemType(newItem); + } } \ No newline at end of file