From 153dc318d9d2c6fb9221b2a64bc3f377548e3338 Mon Sep 17 00:00:00 2001 From: ECWireless Date: Wed, 10 Jul 2024 16:32:11 -0600 Subject: [PATCH] Deploy items dynamically from json file --- .../client/src/contexts/CharacterContext.tsx | 10 +- .../client/src/pages/CharacterCreation.tsx | 100 +++- packages/client/src/utils/types.ts | 11 + packages/contracts/items.json | 47 ++ .../CharacterSystem.sol/CharacterSystem.json | 2 +- .../contracts/out/IWorld.sol/IWorld.abi.json | 38 +- .../out/IWorld.sol/IWorld.abi.json.d.ts | 38 +- packages/contracts/out/IWorld.sol/IWorld.json | 2 +- .../out/MapSystem.sol/MapSystem.json | 2 +- packages/contracts/script/PostDeploy.s.sol | 500 ++++++++++-------- packages/contracts/src/interfaces/Structs.sol | 130 ++--- packages/contracts/test/ItemsSystem.t.sol | 204 ++++--- 12 files changed, 631 insertions(+), 453 deletions(-) create mode 100644 packages/contracts/items.json diff --git a/packages/client/src/contexts/CharacterContext.tsx b/packages/client/src/contexts/CharacterContext.tsx index 5e171f415..56eff413a 100644 --- a/packages/client/src/contexts/CharacterContext.tsx +++ b/packages/client/src/contexts/CharacterContext.tsx @@ -70,7 +70,7 @@ export const CharacterProvider = ({ const [isRefreshing, setIsRefreshing] = useState(false); - const getCharacterData = useCallback(async () => { + const refreshCharacterData = useCallback(async () => { if (!(delegatorAddress && publicClient && worldContract)) return; const characterComponent = Array.from( runQuery([ @@ -174,18 +174,18 @@ export const CharacterProvider = ({ const refreshCharacter = useCallback(async () => { setIsRefreshing(true); try { - await getCharacterData(); + await refreshCharacterData(); } catch (error) { renderError('Error refreshing character'); } finally { setIsRefreshing(false); } - }, [getCharacterData, renderError]); + }, [refreshCharacterData, renderError]); useEffect(() => { if (!(delegatorAddress && publicClient && worldContract)) return; - getCharacterData(); - }, [delegatorAddress, getCharacterData, publicClient, worldContract]); + refreshCharacterData(); + }, [delegatorAddress, refreshCharacterData, publicClient, worldContract]); return ( { const navigate = useNavigate(); @@ -42,6 +49,7 @@ export const CharacterCreation = (): JSX.Element => { components: { UltimateDominionConfig }, delegatorAddress, isSynced, + network: { publicClient, worldContract }, systemCalls: { enterGame, mintCharacter, rollStats }, } = useMUD(); const { character, characterStats, isRefreshing, refreshCharacter } = @@ -59,6 +67,7 @@ export const CharacterCreation = (): JSX.Element => { const [characterClass, setCharacterClass] = useState( StatsClasses.Warrior, ); + const [starterWeapons, setStarterWeapons] = useState(null); const [isCreating, setIsCreating] = useState(false); const [showError, setShowError] = useState(false); @@ -75,6 +84,72 @@ export const CharacterCreation = (): JSX.Element => { setShowError(false); }, [avatar, description, name]); + const fetchStarterWeapons = useCallback(async () => { + try { + const _items: Weapon[] = await Promise.all( + STARTER_WEAPON_IDS.map(async itemId => { + const itemTemplateStats = await worldContract.read.UD__getWeaponStats( + [itemId], + ); + + const itemsContractAddress = + await worldContract.read.UD__getItemsContract(); + + const itemsToken = getContract({ + address: itemsContractAddress, + abi: [ + { + constant: true, + inputs: [ + { + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'uri', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + ], + client: publicClient, + }); + + const metadataURI = await itemsToken.read.uri([itemId]); + const fetachedMetadata = await fetchMetadataFromUri( + uriToHttp(metadataURI)[0], + ); + + return { + agiModifier: itemTemplateStats.agiModifier.toString(), + classRestrictions: itemTemplateStats.classRestrictions, + hitPointModifier: itemTemplateStats.hitPointModifier.toString(), + intModifier: itemTemplateStats.intModifier.toString(), + maxDamage: itemTemplateStats.maxDamage.toString(), + minDamage: itemTemplateStats.minDamage.toString(), + minLevel: itemTemplateStats.minLevel.toString(), + strModifier: itemTemplateStats.strModifier.toString(), + ...fetachedMetadata, + } as Weapon; + }), + ); + + setStarterWeapons(_items); + } catch (error) { + renderError(error, 'Error fetching starter item.'); + } + }, [publicClient, renderError, worldContract]); + + useEffect(() => { + fetchStarterWeapons(); + }, [fetchStarterWeapons]); + const onUploadAvatar = useCallback(() => { const input = document.getElementById('avatarInput'); @@ -527,13 +602,20 @@ export const CharacterCreation = (): JSX.Element => { Items 1 - - - - Rusty Dagger - STR+1 AGI+3 INT+4 - - + {starterWeapons && starterWeapons[characterClass] && ( + + + + {starterWeapons[characterClass].name} + + STR+ + {starterWeapons[characterClass].strModifier} AGI+ + {starterWeapons[characterClass].agiModifier} INT+ + {starterWeapons[characterClass].intModifier} + + + + )} =0.8.24; -import {Script} from "forge-std/Script.sol"; -import {console} from "forge-std/console.sol"; -import {StoreSwitch} from "@latticexyz/store/src/StoreSwitch.sol"; -import {StoreCore, EncodedLengths} from "@latticexyz/store/src/StoreCore.sol"; -import {MockEntropy} from "@test/mocks/MockEntropy.sol"; -import {PuppetModule} from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol"; -import {Systems} from "@latticexyz/world/src/codegen/tables/Systems.sol"; -import {IWorld} from "@world/IWorld.sol"; -import {UltimateDominionConfig, Levels, MapConfig} from "@codegen/index.sol"; -import {ResourceIdLib} from "@latticexyz/store/src/ResourceId.sol"; -import {ResourceId, WorldResourceIdLib, WorldResourceIdInstance} from "@latticexyz/world/src/WorldResourceId.sol"; -import {RESOURCE_SYSTEM} from "@latticexyz/world/src/worldResourceTypes.sol"; -import {RngSystem} from "../src/systems/RngSystem.sol"; -import {IERC721Mintable} from "@latticexyz/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol"; -import {registerERC721} from "@latticexyz/world-modules/src/modules/erc721-puppet/registerERC721.sol"; -import {ERC721System} from "@latticexyz/world-modules/src/modules/erc721-puppet/ERC721System.sol"; -import {ERC721MetadataData} from "@latticexyz/world-modules/src/modules/erc721-puppet/tables/ERC721Metadata.sol"; -import { - GOLD_NAMESPACE, - CHARACTERS_NAMESPACE, - ERC721_NAME, - ERC721_SYMBOL, - ITEMS_NAMESPACE, - TOKEN_URI -} from "../constants.sol"; -import {NoTransferHook} from "../src/NoTransferHook.sol"; -import {BEFORE_CALL_SYSTEM} from "@latticexyz/world/src/systemHookTypes.sol"; -import {Classes, ItemType, MobType} from "@codegen/common.sol"; -import {WeaponStats, MonsterStats, MonsterTemplateDetails} from "@interfaces/Structs.sol"; -import {IERC20Mintable} from "@latticexyz/world-modules/src/modules/erc20-puppet/IERC20Mintable.sol"; -import {ERC20MetadataData} from "@latticexyz/world-modules/src/modules/erc20-puppet/tables/ERC20Metadata.sol"; -import {ERC20System} from "@latticexyz/world-modules/src/modules/erc20-puppet/ERC20System.sol"; -import {registerERC20} from "@latticexyz/world-modules/src/modules/erc20-puppet/registerERC20.sol"; -import {System} from "@latticexyz/world/src/System.sol"; -import {CharacterSystem} from "../src/systems/CharacterSystem.sol"; +import { Script } from "forge-std/Script.sol"; +import { console } from "forge-std/console.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore, EncodedLengths } from "@latticexyz/store/src/StoreCore.sol"; +import { MockEntropy } from "@test/mocks/MockEntropy.sol"; +import { PuppetModule } from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol"; +import { Systems } from "@latticexyz/world/src/codegen/tables/Systems.sol"; +import { IWorld } from "@world/IWorld.sol"; +import { UltimateDominionConfig, Levels, MapConfig } from "@codegen/index.sol"; +import { ResourceIdLib } from "@latticexyz/store/src/ResourceId.sol"; +import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; +import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; +import { RngSystem } from "../src/systems/RngSystem.sol"; +import { IERC721Mintable } from "@latticexyz/world-modules/src/modules/erc721-puppet/IERC721Mintable.sol"; +import { registerERC721 } from "@latticexyz/world-modules/src/modules/erc721-puppet/registerERC721.sol"; +import { ERC721System } from "@latticexyz/world-modules/src/modules/erc721-puppet/ERC721System.sol"; +import { ERC721MetadataData } from "@latticexyz/world-modules/src/modules/erc721-puppet/tables/ERC721Metadata.sol"; +import { GOLD_NAMESPACE, CHARACTERS_NAMESPACE, ERC721_NAME, ERC721_SYMBOL, ITEMS_NAMESPACE, TOKEN_URI } from "../constants.sol"; +import { NoTransferHook } from "../src/NoTransferHook.sol"; +import { BEFORE_CALL_SYSTEM } from "@latticexyz/world/src/systemHookTypes.sol"; +import { Classes, ItemType, MobType } from "@codegen/common.sol"; +import { WeaponStats, MonsterStats, MonsterTemplateDetails, WeaponTemplateDetails } from "@interfaces/Structs.sol"; +import { IERC20Mintable } from "@latticexyz/world-modules/src/modules/erc20-puppet/IERC20Mintable.sol"; +import { ERC20MetadataData } from "@latticexyz/world-modules/src/modules/erc20-puppet/tables/ERC20Metadata.sol"; +import { ERC20System } from "@latticexyz/world-modules/src/modules/erc20-puppet/ERC20System.sol"; +import { registerERC20 } from "@latticexyz/world-modules/src/modules/erc20-puppet/registerERC20.sol"; +import { System } from "@latticexyz/world/src/System.sol"; +import { CharacterSystem } from "../src/systems/CharacterSystem.sol"; import { ERC1155Module } from "@erc1155/ERC1155Module.sol"; import { ERC1155System } from "@erc1155/ERC1155System.sol"; @@ -49,33 +42,33 @@ import "forge-std/console2.sol"; import "forge-std/StdJson.sol"; struct ResourceIds { - ResourceId erc721SystemId; - ResourceId erc721NamespaceId; - ResourceId characterSystemId; - ResourceId erc20SystemId; - ResourceId erc20NamespaceId; - ResourceId rngSystemId; - ResourceId erc1155SystemId; - ResourceId erc1155NamespaceId; - ResourceId itemsSystemId; - ResourceId combatSystemId; + ResourceId erc721SystemId; + ResourceId erc721NamespaceId; + ResourceId characterSystemId; + ResourceId erc20SystemId; + ResourceId erc20NamespaceId; + ResourceId rngSystemId; + ResourceId erc1155SystemId; + ResourceId erc1155NamespaceId; + ResourceId itemsSystemId; + ResourceId combatSystemId; } contract PostDeploy is Script { - using stdJson for string; + using stdJson for string; - IWorld public world; - ResourceIds public resourceIds; - address public worldAddress; + IWorld public world; + ResourceIds public resourceIds; + address public worldAddress; - function run(address _worldAddress) external { - worldAddress = _worldAddress; - world = IWorld(worldAddress); - // Specify a store so that you can use tables directly in PostDeploy - StoreSwitch.setStoreAddress(worldAddress); + function run(address _worldAddress) external { + worldAddress = _worldAddress; + world = IWorld(worldAddress); + // Specify a store so that you can use tables directly in PostDeploy + StoreSwitch.setStoreAddress(worldAddress); - // Load the private key from the `PRIVATE_KEY` environment variable (in .env) - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + // Load the private key from the `PRIVATE_KEY` environment variable (in .env) + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); // Start broadcasting transactions from the deployer account vm.startBroadcast(deployerPrivateKey); @@ -96,188 +89,235 @@ contract PostDeploy is Script { UltimateDominionConfig.setPythProvider(0x52DeaA1c84233F7bb8C8A45baeDE41091c616506); } - uint16 height = uint16(10); - uint16 width = uint16(10); - MapConfig.set(width, height); - - //install puppet - world.installModule(new PuppetModule(), new bytes(0)); - - _addRngSystem(); - - // install gold module - IERC20Mintable goldToken = registerERC20( - world, GOLD_NAMESPACE, ERC20MetadataData({decimals: 18, name: "GoldToken", symbol: unicode"🜚"}) - ); - - UltimateDominionConfig.setGoldToken(address(goldToken)); - - // characters - IERC721Mintable characters = registerERC721( - world, - CHARACTERS_NAMESPACE, - ERC721MetadataData({name: ERC721_NAME, symbol: ERC721_SYMBOL, baseURI: TOKEN_URI}) - ); - - UltimateDominionConfig.setCharacterToken(address(characters)); - - { - resourceIds.erc20NamespaceId = WorldResourceIdLib.encodeNamespace(GOLD_NAMESPACE); - resourceIds.erc20SystemId = - WorldResourceIdLib.encode({typeId: RESOURCE_SYSTEM, namespace: "Gold", name: "GoldToken"}); - - resourceIds.characterSystemId = - WorldResourceIdLib.encode({typeId: RESOURCE_SYSTEM, namespace: "UD", name: "CharacterSystem"}); - - resourceIds.erc721NamespaceId = WorldResourceIdLib.encodeNamespace(CHARACTERS_NAMESPACE); - - resourceIds.erc721SystemId = - WorldResourceIdLib.encode({typeId: RESOURCE_SYSTEM, namespace: "Characters", name: "ERC721System"}); - resourceIds.combatSystemId = - WorldResourceIdLib.encode({typeId: RESOURCE_SYSTEM, namespace: "UD", name: "CombatSystem"}); - resourceIds.erc1155SystemId = _erc1155SystemId(ITEMS_NAMESPACE); - resourceIds.erc1155NamespaceId = WorldResourceIdLib.encodeNamespace(ITEMS_NAMESPACE); - resourceIds.itemsSystemId = - WorldResourceIdLib.encode({typeId: RESOURCE_SYSTEM, namespace: "UD", name: "ItemsSystem"}); - } - - address characterSystemAddress = Systems.getSystem(resourceIds.characterSystemId); - - System goldSystemContract = new ERC20System(); - - world.registerSystem(resourceIds.erc20SystemId, goldSystemContract, true); - - IWorld(worldAddress).grantAccess(resourceIds.erc20NamespaceId, worldAddress); - IWorld(worldAddress).registerFunctionSelector(resourceIds.erc20SystemId, "mint(address,uint256)"); - - world.transferOwnership(resourceIds.erc20NamespaceId, address(characterSystemAddress)); - - System systemContract = new ERC721System(); - world.registerSystem(resourceIds.erc721SystemId, systemContract, true); - - NoTransferHook characterHook = new NoTransferHook(); - - world.registerSystemHook(resourceIds.erc721SystemId, characterHook, BEFORE_CALL_SYSTEM); - - // Transfer characters namespace to World - world.grantAccess(resourceIds.erc721SystemId, worldAddress); - world.grantAccess(resourceIds.erc721SystemId, characterSystemAddress); - world.transferOwnership(resourceIds.erc721NamespaceId, characterSystemAddress); + uint16 height = uint16(10); + uint16 width = uint16(10); + MapConfig.set(width, height); + + //install puppet + world.installModule(new PuppetModule(), new bytes(0)); + + _addRngSystem(); + + // install gold module + IERC20Mintable goldToken = registerERC20( + world, + GOLD_NAMESPACE, + ERC20MetadataData({ decimals: 18, name: "GoldToken", symbol: unicode"🜚" }) + ); + + UltimateDominionConfig.setGoldToken(address(goldToken)); + + // characters + IERC721Mintable characters = registerERC721( + world, + CHARACTERS_NAMESPACE, + ERC721MetadataData({ name: ERC721_NAME, symbol: ERC721_SYMBOL, baseURI: TOKEN_URI }) + ); + + UltimateDominionConfig.setCharacterToken(address(characters)); + + { + resourceIds.erc20NamespaceId = WorldResourceIdLib.encodeNamespace(GOLD_NAMESPACE); + resourceIds.erc20SystemId = WorldResourceIdLib.encode({ + typeId: RESOURCE_SYSTEM, + namespace: "Gold", + name: "GoldToken" + }); + + resourceIds.characterSystemId = WorldResourceIdLib.encode({ + typeId: RESOURCE_SYSTEM, + namespace: "UD", + name: "CharacterSystem" + }); + + resourceIds.erc721NamespaceId = WorldResourceIdLib.encodeNamespace(CHARACTERS_NAMESPACE); + + resourceIds.erc721SystemId = WorldResourceIdLib.encode({ + typeId: RESOURCE_SYSTEM, + namespace: "Characters", + name: "ERC721System" + }); + resourceIds.combatSystemId = WorldResourceIdLib.encode({ + typeId: RESOURCE_SYSTEM, + namespace: "UD", + name: "CombatSystem" + }); + resourceIds.erc1155SystemId = _erc1155SystemId(ITEMS_NAMESPACE); + resourceIds.erc1155NamespaceId = WorldResourceIdLib.encodeNamespace(ITEMS_NAMESPACE); + resourceIds.itemsSystemId = WorldResourceIdLib.encode({ + typeId: RESOURCE_SYSTEM, + namespace: "UD", + name: "ItemsSystem" + }); + } - address items = _deployErc1155(world, ITEMS_NAMESPACE); - UltimateDominionConfig.setItems(address(items)); - //allow entropy system to call callback on Combat system - world.grantAccess(resourceIds.combatSystemId, UltimateDominionConfig.getEntropy()); - _createStarterItems(); - _createMonsters(); - setLevels(); - vm.stopBroadcast(); + address characterSystemAddress = Systems.getSystem(resourceIds.characterSystemId); + + System goldSystemContract = new ERC20System(); + + world.registerSystem(resourceIds.erc20SystemId, goldSystemContract, true); + + IWorld(worldAddress).grantAccess(resourceIds.erc20NamespaceId, worldAddress); + IWorld(worldAddress).registerFunctionSelector(resourceIds.erc20SystemId, "mint(address,uint256)"); + + world.transferOwnership(resourceIds.erc20NamespaceId, address(characterSystemAddress)); + + System systemContract = new ERC721System(); + world.registerSystem(resourceIds.erc721SystemId, systemContract, true); + + NoTransferHook characterHook = new NoTransferHook(); + + world.registerSystemHook(resourceIds.erc721SystemId, characterHook, BEFORE_CALL_SYSTEM); + + // Transfer characters namespace to World + world.grantAccess(resourceIds.erc721SystemId, worldAddress); + world.grantAccess(resourceIds.erc721SystemId, characterSystemAddress); + world.transferOwnership(resourceIds.erc721NamespaceId, characterSystemAddress); + + address items = _deployErc1155(world, ITEMS_NAMESPACE); + UltimateDominionConfig.setItems(address(items)); + //allow entropy system to call callback on Combat system + world.grantAccess(resourceIds.combatSystemId, UltimateDominionConfig.getEntropy()); + _createStarterItems(); + _createMonsters(); + setLevels(); + vm.stopBroadcast(); + } + + function _deployErc1155(IWorld _world, bytes14 itemsNamespace) internal returns (address) { + string memory json = vm.readFile("items.json"); + string memory metadataUriPrefix = json.readString(".metadataUriPrefix"); + + IERC1155 _items = registerERC1155(_world, itemsNamespace, metadataUriPrefix); + + // ERC1155System erc1155System = new ERC1155System(); + address itemsSystemAddress = Systems.getSystem(resourceIds.itemsSystemId); + + // _world.registerSystem(resourceIds.erc1155SystemId, erc1155System, false); + _world.grantAccess(resourceIds.erc1155SystemId, worldAddress); + world.transferOwnership(resourceIds.erc1155NamespaceId, itemsSystemAddress); + return address(_items); + } + + function _addRngSystem() internal { + System rngSystem = new RngSystem(); + + resourceIds.rngSystemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "", "RngSystem"); + + world.registerSystem(resourceIds.rngSystemId, rngSystem, true); + world.registerRootFunctionSelector( + resourceIds.rngSystemId, + "getRng(bytes32,uint8,bytes)", + "getRng(bytes32,uint8,bytes)" + ); + world.registerRootFunctionSelector( + resourceIds.rngSystemId, + "entropyCallback(uint64,address,bytes32)", + "entropyCallback(uint64,address,bytes32)" + ); + world.registerRootFunctionSelector( + resourceIds.rngSystemId, + "_entropyCallback(uint64,address,bytes32)", + "_entropyCallback(uint64,address,bytes32)" + ); + world.registerRootFunctionSelector(resourceIds.rngSystemId, "getFee()", "getFee()"); + world.registerRootFunctionSelector(resourceIds.rngSystemId, "getEntropy()", "getEntropy()"); + } + + function _createStarterItems() internal { + string memory json = vm.readFile("items.json"); + bytes memory itemStatsData = vm.parseJson(json, ".items"); + + WeaponTemplateDetails[] memory itemTemplateDetails = abi.decode(itemStatsData, (WeaponTemplateDetails[])); + + uint256[] memory warriorItemIds = new uint256[](1); + uint256[] memory rogueItemIds = new uint256[](1); + uint256[] memory mageItemIds = new uint256[](1); + + for (uint256 i = 0; i < itemTemplateDetails.length; i++) { + WeaponTemplateDetails memory itemTemplate = itemTemplateDetails[i]; + + WeaponStats memory newWeapon = WeaponStats({ + agiModifier: itemTemplate.stats.agiModifier, + classRestrictions: itemTemplate.stats.classRestrictions, + hitPointModifier: itemTemplate.stats.hitPointModifier, + intModifier: itemTemplate.stats.intModifier, + maxDamage: itemTemplate.stats.maxDamage, + minDamage: itemTemplate.stats.minDamage, + minLevel: itemTemplate.stats.minLevel, + strModifier: itemTemplate.stats.strModifier + }); + + uint256 starterItemId = world.UD__createItem( + ItemType.Weapon, + 10 ether, + abi.encode(newWeapon), + itemTemplate.metadataUri + ); + + if (i == 0) { + warriorItemIds[0] = starterItemId; + } + if (i == 1) { + rogueItemIds[0] = starterItemId; + } + if (i == 2) { + mageItemIds[0] = starterItemId; + } } - function _deployErc1155(IWorld _world, bytes14 itemsNamespace) internal returns (address) { - IERC1155 _items = registerERC1155(_world, itemsNamespace, "test_Items_uri/"); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; - // ERC1155System erc1155System = new ERC1155System(); - address itemsSystemAddress = Systems.getSystem(resourceIds.itemsSystemId); + world.UD__setStarterItems(Classes.Warrior, warriorItemIds, amounts); + world.UD__setStarterItems(Classes.Rogue, rogueItemIds, amounts); + world.UD__setStarterItems(Classes.Mage, mageItemIds, amounts); + } - // _world.registerSystem(resourceIds.erc1155SystemId, erc1155System, false); - _world.grantAccess(resourceIds.erc1155SystemId, worldAddress); - world.transferOwnership(resourceIds.erc1155NamespaceId, itemsSystemAddress); - return address(_items); - } + function _createMonsters() internal { + string memory json = vm.readFile("monsters.json"); + bytes memory monsterStatsData = vm.parseJson(json, ".monsters"); - function _addRngSystem() internal { - System rngSystem = new RngSystem(); - - resourceIds.rngSystemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "", "RngSystem"); - - world.registerSystem(resourceIds.rngSystemId, rngSystem, true); - world.registerRootFunctionSelector( - resourceIds.rngSystemId, "getRng(bytes32,uint8,bytes)", "getRng(bytes32,uint8,bytes)" - ); - world.registerRootFunctionSelector( - resourceIds.rngSystemId, - "entropyCallback(uint64,address,bytes32)", - "entropyCallback(uint64,address,bytes32)" - ); - world.registerRootFunctionSelector( - resourceIds.rngSystemId, - "_entropyCallback(uint64,address,bytes32)", - "_entropyCallback(uint64,address,bytes32)" - ); - world.registerRootFunctionSelector(resourceIds.rngSystemId, "getFee()", "getFee()"); - world.registerRootFunctionSelector(resourceIds.rngSystemId, "getEntropy()", "getEntropy()"); - } + MonsterTemplateDetails[] memory monsterTemplateDetails = abi.decode(monsterStatsData, (MonsterTemplateDetails[])); - function _createStarterItems() internal { - uint8[] memory restrictions = new uint8[](0); - WeaponStats memory weaponStats = WeaponStats({ - minDamage: 1, - maxDamage: 4, - classRestrictions: restrictions, - minLevel: 0, - strModifier: 0, - agiModifier: 0, - intModifier: 0, - hitPointModifier: 0 - }); - - uint256 starterItemId = - world.UD__createItem(ItemType.Weapon, 10 ether, abi.encode(weaponStats), "starter-weapon-uri/"); - uint256[] memory itemIds = new uint256[](1); - itemIds[0] = starterItemId; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 1; - world.UD__setStarterItems(Classes.Rogue, itemIds, amounts); - world.UD__setStarterItems(Classes.Warrior, itemIds, amounts); - world.UD__setStarterItems(Classes.Mage, itemIds, amounts); - } + for (uint256 i = 0; i < monsterTemplateDetails.length; i++) { + MonsterTemplateDetails memory monsterTemplate = monsterTemplateDetails[i]; - function _createMonsters() internal { - string memory json = vm.readFile("monsters.json"); - bytes memory monsterStatsData = vm.parseJson(json, ".monsters"); - - MonsterTemplateDetails[] memory monsterTemplateDetails = - abi.decode(monsterStatsData, (MonsterTemplateDetails[])); - - for (uint256 i = 0; i < monsterTemplateDetails.length; i++) { - MonsterTemplateDetails memory monsterTemplate = monsterTemplateDetails[i]; - - MonsterStats memory newMonster = MonsterStats({ - agility: monsterTemplate.stats.agility, - armor: monsterTemplate.stats.armor, - class: monsterTemplate.stats.class, - experience: monsterTemplate.stats.experience, - hitPoints: monsterTemplate.stats.hitPoints, - level: monsterTemplate.stats.level, - intelligence: monsterTemplate.stats.intelligence, - inventory: monsterTemplate.stats.inventory, - strength: monsterTemplate.stats.strength - }); - - world.UD__createMob(MobType.Monster, abi.encode(newMonster), monsterTemplate.metadataUri); - } - } + MonsterStats memory newMonster = MonsterStats({ + agility: monsterTemplate.stats.agility, + armor: monsterTemplate.stats.armor, + class: monsterTemplate.stats.class, + experience: monsterTemplate.stats.experience, + hitPoints: monsterTemplate.stats.hitPoints, + level: monsterTemplate.stats.level, + intelligence: monsterTemplate.stats.intelligence, + inventory: monsterTemplate.stats.inventory, + strength: monsterTemplate.stats.strength + }); - function setLevels() internal { - Levels.setExperience(1, 300); - Levels.setExperience(2, 900); - Levels.setExperience(3, 2700); - Levels.setExperience(4, 6500); - Levels.setExperience(5, 14000); - Levels.setExperience(6, 23000); - Levels.setExperience(7, 34000); - Levels.setExperience(8, 48000); - Levels.setExperience(9, 64000); - Levels.setExperience(10, 85000); - Levels.setExperience(11, 100000); - Levels.setExperience(12, 120000); - Levels.setExperience(13, 140000); - Levels.setExperience(14, 165000); - Levels.setExperience(15, 195000); - Levels.setExperience(16, 225000); - Levels.setExperience(17, 265000); - Levels.setExperience(18, 305000); - Levels.setExperience(19, 355000); + world.UD__createMob(MobType.Monster, abi.encode(newMonster), monsterTemplate.metadataUri); } + } + + function setLevels() internal { + Levels.setExperience(1, 300); + Levels.setExperience(2, 900); + Levels.setExperience(3, 2700); + Levels.setExperience(4, 6500); + Levels.setExperience(5, 14000); + Levels.setExperience(6, 23000); + Levels.setExperience(7, 34000); + Levels.setExperience(8, 48000); + Levels.setExperience(9, 64000); + Levels.setExperience(10, 85000); + Levels.setExperience(11, 100000); + Levels.setExperience(12, 120000); + Levels.setExperience(13, 140000); + Levels.setExperience(14, 165000); + Levels.setExperience(15, 195000); + Levels.setExperience(16, 225000); + Levels.setExperience(17, 265000); + Levels.setExperience(18, 305000); + Levels.setExperience(19, 355000); + } } diff --git a/packages/contracts/src/interfaces/Structs.sol b/packages/contracts/src/interfaces/Structs.sol index 3940b6d25..7db83e89a 100644 --- a/packages/contracts/src/interfaces/Structs.sol +++ b/packages/contracts/src/interfaces/Structs.sol @@ -1,92 +1,98 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.24; -import {ItemType, Classes, Alignment} from "@codegen/common.sol"; +import { ItemType, Classes, Alignment } from "@codegen/common.sol"; struct WeaponStats { - uint256 minDamage; - uint256 maxDamage; - uint8[] classRestrictions; - uint256 minLevel; - int256 strModifier; - int256 agiModifier; - int256 intModifier; - int256 hitPointModifier; + int256 agiModifier; + uint8[] classRestrictions; + int256 hitPointModifier; + int256 intModifier; + uint256 maxDamage; + uint256 minDamage; + uint256 minLevel; + int256 strModifier; +} + +struct WeaponTemplateDetails { + string metadataUri; + string name; + WeaponStats stats; } struct ArmorStats { - uint256 armorModifier; - uint8[] classRestrictions; - uint256 minLevel; - int256 strModifier; - int256 agiModifier; - int256 intModifier; - int256 hitPointModifier; + uint256 armorModifier; + uint8[] classRestrictions; + uint256 minLevel; + int256 strModifier; + int256 agiModifier; + int256 intModifier; + int256 hitPointModifier; } struct MonsterStats { - //base to hit number for this mob for physical attacks = agility * physicalAttackConversion - uint256 agility; - // damage reduction: subtracted from total damage - uint256 armor; - // monster's class - Classes class; - // the amount of experience this monster is worth - uint256 experience; - // hit points - uint256 hitPoints; - // base to hit modifier for magical Attacks = inteligence * magicDefenseConversion - uint256 intelligence; - // item ids of potential drops - uint256[] inventory; - // monster level - uint256 level; - // base damage = strength * damangeConversion - uint256 strength; + //base to hit number for this mob for physical attacks = agility * physicalAttackConversion + uint256 agility; + // damage reduction: subtracted from total damage + uint256 armor; + // monster's class + Classes class; + // the amount of experience this monster is worth + uint256 experience; + // hit points + uint256 hitPoints; + // base to hit modifier for magical Attacks = inteligence * magicDefenseConversion + uint256 intelligence; + // item ids of potential drops + uint256[] inventory; + // monster level + uint256 level; + // base damage = strength * damangeConversion + uint256 strength; } struct MonsterTemplateDetails { - string metadataUri; - string name; - MonsterStats stats; + string metadataUri; + string name; + MonsterStats stats; } struct PhysicalAttackStats { - // additional damage on top of item damage - int256 bonusDamage; - // base armor penetration - int256 armorPenetration; - //bonus chance to hit - int256 attackModifierBonus; - // crit chance - int256 critChanceBonus; + // additional damage on top of item damage + int256 bonusDamage; + // base armor penetration + int256 armorPenetration; + //bonus chance to hit + int256 attackModifierBonus; + // crit chance + int256 critChanceBonus; } struct Action { - bytes32 attackerEntityId; - bytes32 defenderEntityId; - bytes32 actionId; - uint256 weaponId; + bytes32 attackerEntityId; + bytes32 defenderEntityId; + bytes32 actionId; + uint256 weaponId; } struct MagicAttackStats { - // additional damage on top of item damage - uint256 bonusDamage; - // list of items that can deal this attack - uint256[] requiredItems; - // base armor penetration - uint256 armorPenetration; + // additional damage on top of item damage + uint256 bonusDamage; + // list of items that can deal this attack + uint256[] requiredItems; + // base armor penetration + uint256 armorPenetration; } struct NPCStats { - string name; - bytes32[] storyPathIds; - Alignment alignment; + string name; + bytes32[] storyPathIds; + Alignment alignment; } struct QuestEntity { - // entity id is a keccak256(characterId, questId)) - bytes32 entityId; - uint256 questId; - uint256 currentStep; + // entity id is a keccak256(characterId, questId)) + bytes32 entityId; + uint256 questId; + uint256 currentStep; } diff --git a/packages/contracts/test/ItemsSystem.t.sol b/packages/contracts/test/ItemsSystem.t.sol index ea940b1fd..a986ba20c 100644 --- a/packages/contracts/test/ItemsSystem.t.sol +++ b/packages/contracts/test/ItemsSystem.t.sol @@ -1,121 +1,113 @@ +// SPDX-License-Identifier: MIT pragma solidity >=0.8.24; -import {SetUp} from "./SetUp.sol"; -import {Classes, ItemType} from "@codegen/common.sol"; -import {StatsData} from "@tables/Stats.sol"; +import { SetUp } from "./SetUp.sol"; +import { Classes, ItemType } from "@codegen/common.sol"; +import { StatsData } from "@tables/Stats.sol"; import "forge-std/console2.sol"; -import {PuppetModule} from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol"; -import {UltimateDominionConfig} from "@codegen/index.sol"; -import {UltimateDominionConfigSystem} from "@systems/UltimateDominionConfigSystem.sol"; -import {ERC1155Module} from "@erc1155/ERC1155Module.sol"; -import {ERC1155System} from "@erc1155/ERC1155System.sol"; -import {IERC1155MetadataURI} from "@erc1155/IERC1155MetadataURI.sol"; -import {IERC1155} from "@erc1155/IERC1155.sol"; -import {registerERC1155} from "@erc1155/registerERC1155.sol"; -import {_erc1155SystemId} from "@erc1155/utils.sol"; -import {WeaponStats} from "@interfaces/Structs.sol"; -import {ResourceIdLib} from "@latticexyz/store/src/ResourceId.sol"; -import {ResourceId, WorldResourceIdLib, WorldResourceIdInstance} from "@latticexyz/world/src/WorldResourceId.sol"; -import {_itemsSystemId} from "../src/utils.sol"; -import { - GOLD_NAMESPACE, - CHARACTERS_NAMESPACE, - ERC721_NAME, - ERC721_SYMBOL, - TOKEN_URI, - ITEMS_NAMESPACE -} from "../constants.sol"; -import {GasReporter} from "@latticexyz/gas-report/src/GasReporter.sol"; +import { PuppetModule } from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol"; +import { UltimateDominionConfig } from "@codegen/index.sol"; +import { UltimateDominionConfigSystem } from "@systems/UltimateDominionConfigSystem.sol"; +import { ERC1155Module } from "@erc1155/ERC1155Module.sol"; +import { ERC1155System } from "@erc1155/ERC1155System.sol"; +import { IERC1155MetadataURI } from "@erc1155/IERC1155MetadataURI.sol"; +import { IERC1155 } from "@erc1155/IERC1155.sol"; +import { registerERC1155 } from "@erc1155/registerERC1155.sol"; +import { _erc1155SystemId } from "@erc1155/utils.sol"; +import { WeaponStats } from "@interfaces/Structs.sol"; +import { ResourceIdLib } from "@latticexyz/store/src/ResourceId.sol"; +import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; +import { _itemsSystemId } from "../src/utils.sol"; +import { GOLD_NAMESPACE, CHARACTERS_NAMESPACE, ERC721_NAME, ERC721_SYMBOL, TOKEN_URI, ITEMS_NAMESPACE } from "../constants.sol"; +import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; contract Test_ItemsSystem is SetUp, GasReporter { - function setUp() public virtual override { - super.setUp(); - vm.prank(deployer); - world.grantAccess(_itemsSystemId("UD"), address(this)); - } + function setUp() public virtual override { + super.setUp(); + vm.prank(deployer); + world.grantAccess(_itemsSystemId("UD"), address(this)); + } - function test_CreateItem() public { - startGasReport("creates an item"); + function test_CreateItem() public { + startGasReport("creates an item"); - uint8[] memory restrictions = new uint8[](0); - WeaponStats memory weaponStats = WeaponStats({ - minDamage: 1, - maxDamage: 4, - classRestrictions: restrictions, - minLevel: 0, - strModifier: 0, - agiModifier: 0, - intModifier: 0, - hitPointModifier: 0 - }); - vm.startPrank(deployer); - uint256 firstItemId = - world.UD__createItem(ItemType.Weapon, 10 ether, abi.encode(weaponStats), "test_Weapon_uri1/"); - uint256 newItemId = - world.UD__createItem(ItemType.Weapon, 100 ether, abi.encode(weaponStats), "test_Weapon_uri/"); + uint8[] memory restrictions = new uint8[](0); + WeaponStats memory weaponStats = WeaponStats({ + agiModifier: 0, + classRestrictions: restrictions, + hitPointModifier: 0, + intModifier: 0, + maxDamage: 4, + minDamage: 1, + minLevel: 0, + strModifier: 0 + }); + vm.startPrank(deployer); + uint256 firstItemId = world.UD__createItem(ItemType.Weapon, 10 ether, abi.encode(weaponStats), "test_Weapon_uri1/"); + uint256 newItemId = world.UD__createItem(ItemType.Weapon, 100 ether, abi.encode(weaponStats), "test_Weapon_uri/"); - assertEq(newItemId, 4); - assertEq(world.UD__getTotalSupply(newItemId), 100 ether); - assertEq(world.UD__getTotalSupply(firstItemId), 10 ether); - assertEq( - keccak256(abi.encode(erc1155System.uri(newItemId))), - keccak256(abi.encode("test_Items_uri/test_Weapon_uri/")) - ); + assertEq(newItemId, 4); + assertEq(world.UD__getTotalSupply(newItemId), 100 ether); + assertEq(world.UD__getTotalSupply(firstItemId), 10 ether); + assertEq( + keccak256(abi.encode(erc1155System.uri(newItemId))), + keccak256(abi.encode("test_Items_uri/test_Weapon_uri/")) + ); - endGasReport(); - } + endGasReport(); + } - function test_CreateItem_Revert_NotNamespaceOwner() public { - uint8[] memory restrictions = new uint8[](0); - WeaponStats memory weaponStats = WeaponStats({ - minDamage: 1, - maxDamage: 4, - classRestrictions: restrictions, - minLevel: 0, - strModifier: 0, - agiModifier: 0, - intModifier: 0, - hitPointModifier: 0 - }); - vm.startPrank(alice); - vm.expectRevert(); - world.UD__createItem(ItemType.Weapon, 100 ether, abi.encode(weaponStats), "test_Weapon_uri1/"); - } + function test_CreateItem_Revert_NotNamespaceOwner() public { + uint8[] memory restrictions = new uint8[](0); + WeaponStats memory weaponStats = WeaponStats({ + agiModifier: 0, + classRestrictions: restrictions, + hitPointModifier: 0, + intModifier: 0, + maxDamage: 4, + minDamage: 1, + minLevel: 0, + strModifier: 0 + }); + vm.startPrank(alice); + vm.expectRevert(); + world.UD__createItem(ItemType.Weapon, 100 ether, abi.encode(weaponStats), "test_Weapon_uri1/"); + } - function test_GetTotalSupply() public { - uint8[] memory restrictions = new uint8[](0); - WeaponStats memory weaponStats = WeaponStats({ - minDamage: 1, - maxDamage: 4, - classRestrictions: restrictions, - minLevel: 0, - strModifier: 0, - agiModifier: 0, - intModifier: 0, - hitPointModifier: 0 - }); - vm.startPrank(deployer); - uint256 id = world.UD__createItem(ItemType.Weapon, 100 ether, abi.encode(weaponStats), "test_Weapon_uri/"); - assertEq(world.UD__getTotalSupply(id), 100 ether); - } + function test_GetTotalSupply() public { + uint8[] memory restrictions = new uint8[](0); + WeaponStats memory weaponStats = WeaponStats({ + agiModifier: 0, + classRestrictions: restrictions, + hitPointModifier: 0, + intModifier: 0, + maxDamage: 4, + minDamage: 1, + minLevel: 0, + strModifier: 0 + }); + vm.startPrank(deployer); + uint256 id = world.UD__createItem(ItemType.Weapon, 100 ether, abi.encode(weaponStats), "test_Weapon_uri/"); + assertEq(world.UD__getTotalSupply(id), 100 ether); + } - function test_GetBalance() public { - uint256 fees = entropy.getFee(address(1)); - vm.startPrank(alice); - world.UD__rollStats{value: fees}(alicesRandomness, alicesCharacterId, Classes.Rogue); - world.UD__enterGame(alicesCharacterId); - assertEq(erc1155System.balanceOf(address(alice), 1), 1); - } + function test_GetBalance() public { + uint256 fees = entropy.getFee(address(1)); + vm.startPrank(alice); + world.UD__rollStats{ value: fees }(alicesRandomness, alicesCharacterId, Classes.Rogue); + world.UD__enterGame(alicesCharacterId); + assertEq(erc1155System.balanceOf(address(alice), 1), 1); + } - function test_dropItems() public { - uint256[] memory itemIds = new uint256[](1); - uint256[] memory amounts = new uint256[](1); - bytes32[] memory characterIds = new bytes32[](1); - itemIds[0] = newArmorId; - amounts[0] = 1; - characterIds[0] = alicesCharacterId; - world.UD__dropItems(itemIds, amounts, characterIds); + function test_dropItems() public { + uint256[] memory itemIds = new uint256[](1); + uint256[] memory amounts = new uint256[](1); + bytes32[] memory characterIds = new bytes32[](1); + itemIds[0] = newArmorId; + amounts[0] = 1; + characterIds[0] = alicesCharacterId; + world.UD__dropItems(itemIds, amounts, characterIds); - assertEq(erc1155System.balanceOf(address(alice), newArmorId), 1); - } + assertEq(erc1155System.balanceOf(address(alice), newArmorId), 1); + } }