From 034509a9970c52fd0c528f833524f202c981fc2b Mon Sep 17 00:00:00 2001 From: ECWireless Date: Mon, 5 Aug 2024 08:04:34 -0600 Subject: [PATCH 1/2] Get item stats through decoding bytes --- .../client/src/contexts/CharacterContext.tsx | 61 +++++++-------- packages/client/src/pages/Character.tsx | 55 +++++-------- .../client/src/pages/CharacterCreation.tsx | 36 ++++----- packages/client/src/utils/helpers.ts | 77 ++++++++++++++++++- 4 files changed, 139 insertions(+), 90 deletions(-) diff --git a/packages/client/src/contexts/CharacterContext.tsx b/packages/client/src/contexts/CharacterContext.tsx index 854220102..cc558c980 100644 --- a/packages/client/src/contexts/CharacterContext.tsx +++ b/packages/client/src/contexts/CharacterContext.tsx @@ -16,13 +16,17 @@ import { import { formatEther, hexToString, zeroHash } from 'viem'; import { useToast } from '../hooks/useToast'; -import { fetchMetadataFromUri, uriToHttp } from '../utils/helpers'; +import { + decodeArmorStats, + decodeWeaponStats, + fetchMetadataFromUri, + uriToHttp, +} from '../utils/helpers'; import type { Armor, Character, CharacterData, EntityStats, - StatsClasses, Weapon, } from '../utils/types'; import { useMUD } from './MUDContext'; @@ -55,6 +59,7 @@ export const CharacterProvider = ({ CharacterEquipment, Characters, CharactersTokenURI, + Items, ItemsBaseURI, ItemsOwners, ItemsTokenURI, @@ -238,16 +243,14 @@ export const CharacterProvider = ({ const fullArmor = await Promise.all( _armor.map(async item => { - const itemTemplateStats = - await worldContract.read.UD__getArmorStats([ - BigInt(item.tokenId), - ]); - const tokenIdEntity = encodeEntity( { tokenId: 'uint256' }, { tokenId: BigInt(item.tokenId) }, ); + const itemTemplate = getComponentValueStrict(Items, tokenIdEntity); + const decodedArmorStats = decodeArmorStats(itemTemplate.stats); + const baseURI = getComponentValueStrict( ItemsBaseURI, singletonEntity, @@ -264,18 +267,16 @@ export const CharacterProvider = ({ return { ...metadata, - agiModifier: itemTemplateStats.agiModifier.toString(), - armorModifier: itemTemplateStats.armorModifier.toString(), + agiModifier: decodedArmorStats.agiModifier, + armorModifier: decodedArmorStats.armorModifier, balance: item.balance, - classRestrictions: itemTemplateStats.classRestrictions.map( - (classRestriction: number) => classRestriction as StatsClasses, - ), - hitPointModifier: itemTemplateStats.hitPointModifier.toString(), - intModifier: itemTemplateStats.intModifier.toString(), + classRestrictions: decodedArmorStats.classRestrictions, + hitPointModifier: decodedArmorStats.hitPointModifier, + intModifier: decodedArmorStats.intModifier, itemId: item.itemId, - minLevel: itemTemplateStats.minLevel.toString(), + minLevel: decodedArmorStats.minLevel, owner: item.owner, - strModifier: itemTemplateStats.strModifier.toString(), + strModifier: decodedArmorStats.strModifier, tokenId: item.tokenId, } as Armor; }), @@ -283,16 +284,14 @@ export const CharacterProvider = ({ const fullWeapons = await Promise.all( _weapons.map(async item => { - const itemTemplateStats = - await worldContract.read.UD__getWeaponStats([ - BigInt(item.tokenId), - ]); - const tokenIdEntity = encodeEntity( { tokenId: 'uint256' }, { tokenId: BigInt(item.tokenId) }, ); + const itemTemplate = getComponentValueStrict(Items, tokenIdEntity); + const decodedWeaponStats = decodeWeaponStats(itemTemplate.stats); + const baseURI = getComponentValueStrict( ItemsBaseURI, singletonEntity, @@ -309,19 +308,17 @@ export const CharacterProvider = ({ return { ...metadata, - agiModifier: itemTemplateStats.agiModifier.toString(), + agiModifier: decodedWeaponStats.agiModifier, balance: item.balance, - classRestrictions: itemTemplateStats.classRestrictions.map( - (classRestriction: number) => classRestriction as StatsClasses, - ), - hitPointModifier: itemTemplateStats.hitPointModifier.toString(), - intModifier: itemTemplateStats.intModifier.toString(), + classRestrictions: decodedWeaponStats.classRestrictions, + hitPointModifier: decodedWeaponStats.hitPointModifier, + intModifier: decodedWeaponStats.intModifier, itemId: item.itemId, - maxDamage: itemTemplateStats.maxDamage.toString(), - minDamage: itemTemplateStats.minDamage.toString(), - minLevel: itemTemplateStats.minLevel.toString(), + maxDamage: decodedWeaponStats.maxDamage, + minDamage: decodedWeaponStats.minDamage, + minLevel: decodedWeaponStats.minLevel, owner: item.owner, - strModifier: itemTemplateStats.strModifier.toString(), + strModifier: decodedWeaponStats.strModifier, tokenId: item.tokenId, } as Weapon; }), @@ -336,7 +333,7 @@ export const CharacterProvider = ({ ); } }, - [ItemsBaseURI, ItemsOwners, ItemsTokenURI, renderError, worldContract], + [Items, ItemsBaseURI, ItemsOwners, ItemsTokenURI, renderError], ); useEffect(() => { diff --git a/packages/client/src/pages/Character.tsx b/packages/client/src/pages/Character.tsx index f3527d746..49efa5942 100644 --- a/packages/client/src/pages/Character.tsx +++ b/packages/client/src/pages/Character.tsx @@ -46,7 +46,9 @@ import { useToast } from '../hooks/useToast'; import { HOME_PATH, LEADERBOARD_PATH } from '../Routes'; import { MAX_EQUIPPED_ARMOR, MAX_EQUIPPED_WEAPONS } from '../utils/constants'; import { + decodeArmorStats, decodeCharacterId, + decodeWeaponStats, fetchMetadataFromUri, uriToHttp, } from '../utils/helpers'; @@ -445,7 +447,6 @@ const ItemsPanel = ({ character }: { character: Character }): JSX.Element => { ItemsOwners, ItemsTokenURI, }, - network: { worldContract }, } = useMUD(); const { @@ -499,6 +500,7 @@ const ItemsPanel = ({ character }: { character: Character }): JSX.Element => { itemId: entity, itemType: itemTemplate.itemType, owner, + stats: itemTemplate.stats, tokenId: tokenId.toString(), tokenIdEntity, }; @@ -515,10 +517,7 @@ const ItemsPanel = ({ character }: { character: Character }): JSX.Element => { const fullArmor = await Promise.all( _armor.map(async item => { - const itemTemplateStats = - await worldContract.read.UD__getArmorStats([ - BigInt(item.tokenId), - ]); + const decodedArmorStats = decodeArmorStats(item.stats); const baseURI = getComponentValueStrict( ItemsBaseURI, @@ -536,16 +535,14 @@ const ItemsPanel = ({ character }: { character: Character }): JSX.Element => { return { ...metadata, - agiModifier: itemTemplateStats.agiModifier.toString(), - armorModifier: itemTemplateStats.armorModifier.toString(), - classRestrictions: itemTemplateStats.classRestrictions.map( - (classRestriction: number) => classRestriction as StatsClasses, - ), - hitPointModifier: itemTemplateStats.hitPointModifier.toString(), - intModifier: itemTemplateStats.intModifier.toString(), + agiModifier: decodedArmorStats.agiModifier, + armorModifier: decodedArmorStats.armorModifier, + classRestrictions: decodedArmorStats.classRestrictions, + hitPointModifier: decodedArmorStats.hitPointModifier, + intModifier: decodedArmorStats.intModifier, itemId: item.itemId, owner: item.owner, - strModifier: itemTemplateStats.strModifier.toString(), + strModifier: decodedArmorStats.strModifier, tokenId: item.tokenId, } as Armor; }), @@ -553,10 +550,7 @@ const ItemsPanel = ({ character }: { character: Character }): JSX.Element => { const fullWeapons = await Promise.all( _weapons.map(async item => { - const itemTemplateStats = - await worldContract.read.UD__getWeaponStats([ - BigInt(item.tokenId), - ]); + const decodedWeaponStats = decodeWeaponStats(item.stats); const baseURI = getComponentValueStrict( ItemsBaseURI, @@ -574,19 +568,17 @@ const ItemsPanel = ({ character }: { character: Character }): JSX.Element => { return { ...metadata, - agiModifier: itemTemplateStats.agiModifier.toString(), + agiModifier: decodedWeaponStats.agiModifier, balance: item.balance, - classRestrictions: itemTemplateStats.classRestrictions.map( - (classRestriction: number) => classRestriction as StatsClasses, - ), - hitPointModifier: itemTemplateStats.hitPointModifier.toString(), - intModifier: itemTemplateStats.intModifier.toString(), + classRestrictions: decodedWeaponStats.classRestrictions, + hitPointModifier: decodedWeaponStats.hitPointModifier, + intModifier: decodedWeaponStats.intModifier, itemId: item.itemId, - maxDamage: itemTemplateStats.maxDamage.toString(), - minDamage: itemTemplateStats.minDamage.toString(), - minLevel: itemTemplateStats.minLevel.toString(), + maxDamage: decodedWeaponStats.maxDamage, + minDamage: decodedWeaponStats.minDamage, + minLevel: decodedWeaponStats.minLevel, owner: item.owner, - strModifier: itemTemplateStats.strModifier.toString(), + strModifier: decodedWeaponStats.strModifier, tokenId: item.tokenId, } as Weapon; }), @@ -603,14 +595,7 @@ const ItemsPanel = ({ character }: { character: Character }): JSX.Element => { setIsLoadingItems(false); } }, - [ - Items, - ItemsBaseURI, - ItemsOwners, - ItemsTokenURI, - renderError, - worldContract, - ], + [Items, ItemsBaseURI, ItemsOwners, ItemsTokenURI, renderError], ); useEffect(() => { diff --git a/packages/client/src/pages/CharacterCreation.tsx b/packages/client/src/pages/CharacterCreation.tsx index 88e7f66e7..9c3cb2589 100644 --- a/packages/client/src/pages/CharacterCreation.tsx +++ b/packages/client/src/pages/CharacterCreation.tsx @@ -31,6 +31,7 @@ import { useUploadFile } from '../hooks/useUploadFile'; import { GAME_BOARD_PATH, HOME_PATH } from '../Routes'; import { API_URL } from '../utils/constants'; import { + decodeWeaponStats, fetchMetadataFromUri, shortenAddress, uriToHttp, @@ -44,6 +45,7 @@ export const CharacterCreation = (): JSX.Element => { const { isConnected } = useAccount(); const { components: { + Items, ItemsBaseURI, ItemsTokenURI, StarterItems, @@ -51,7 +53,6 @@ export const CharacterCreation = (): JSX.Element => { }, delegatorAddress, isSynced, - network: { worldContract }, systemCalls: { enterGame, mintCharacter, rollStats }, } = useMUD(); const { character, isRefreshing, refreshCharacter } = useCharacter(); @@ -96,15 +97,14 @@ export const CharacterCreation = (): JSX.Element => { try { const _items: Weapon[] = await Promise.all( starterWeaponTokenIds.map(async tokenId => { - const itemTemplateStats = await worldContract.read.UD__getWeaponStats( - [tokenId], - ); - const tokenIdEntity = encodeEntity( { tokenId: 'uint256' }, - { tokenId: tokenId }, + { tokenId }, ); + const itemTemplate = getComponentValueStrict(Items, tokenIdEntity); + const decodedWeaponStats = decodeWeaponStats(itemTemplate.stats); + const baseURI = getComponentValueStrict( ItemsBaseURI, singletonEntity, @@ -120,14 +120,14 @@ export const CharacterCreation = (): JSX.Element => { ); 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(), + agiModifier: decodedWeaponStats.agiModifier, + classRestrictions: decodedWeaponStats.classRestrictions, + hitPointModifier: decodedWeaponStats.hitPointModifier, + intModifier: decodedWeaponStats.intModifier, + maxDamage: decodedWeaponStats.maxDamage, + minDamage: decodedWeaponStats.minDamage, + minLevel: decodedWeaponStats.minLevel, + strModifier: decodedWeaponStats.strModifier, ...fetachedMetadata, } as Weapon; }), @@ -137,13 +137,7 @@ export const CharacterCreation = (): JSX.Element => { } catch (e) { renderError((e as Error)?.message ?? 'Error fetching starter item.', e); } - }, [ - ItemsBaseURI, - ItemsTokenURI, - renderError, - starterWeaponTokenIds, - worldContract, - ]); + }, [Items, ItemsBaseURI, ItemsTokenURI, renderError, starterWeaponTokenIds]); useEffect(() => { fetchStarterWeapons(); diff --git a/packages/client/src/utils/helpers.ts b/packages/client/src/utils/helpers.ts index a06b5b45e..d82ea9d55 100644 --- a/packages/client/src/utils/helpers.ts +++ b/packages/client/src/utils/helpers.ts @@ -1,6 +1,44 @@ -import { hexToBigInt } from 'viem'; +import { decodeAbiParameters, hexToBigInt } from 'viem'; -import type { Metadata } from '../utils/types'; +import { + type ArmorStats, + type Metadata, + StatsClasses, + WeaponStats, +} from '../utils/types'; + +export const decodeArmorStats = (statsBytes: string): ArmorStats => { + const itemTemplateStats = decodeAbiParameters( + [ + { + name: 'armorStats', + type: 'tuple', + components: [ + { name: 'agiModifier', type: 'int256' }, + { name: 'armorModifier', type: 'uint256' }, + { name: 'classRestrictions', type: 'uint8[]' }, + { name: 'hitPointModifier', type: 'int256' }, + { name: 'intModifier', type: 'int256' }, + { name: 'minLevel', type: 'uint256' }, + { name: 'strModifier', type: 'int256' }, + ], + }, + ], + statsBytes as `0x${string}`, + )[0]; + + return { + agiModifier: itemTemplateStats.agiModifier.toString(), + armorModifier: itemTemplateStats.armorModifier.toString(), + classRestrictions: itemTemplateStats.classRestrictions.map( + (classRestriction: number) => classRestriction as StatsClasses, + ), + hitPointModifier: itemTemplateStats.hitPointModifier.toString(), + intModifier: itemTemplateStats.intModifier.toString(), + minLevel: itemTemplateStats.minLevel.toString(), + strModifier: itemTemplateStats.strModifier.toString(), + }; +}; export const decodeCharacterId = ( characterId: `0x${string}`, @@ -18,6 +56,41 @@ export const decodeCharacterId = ( return { ownerAddress, characterTokenId: characterTokenId.toString() }; }; +export const decodeWeaponStats = (statsBytes: string): WeaponStats => { + const itemTemplateStats = decodeAbiParameters( + [ + { + name: 'weaponStats', + type: 'tuple', + components: [ + { name: 'agiModifier', type: 'int256' }, + { name: 'classRestrictions', type: 'uint8[]' }, + { name: 'hitPointModifier', type: 'int256' }, + { name: 'intModifier', type: 'int256' }, + { name: 'maxDamage', type: 'uint256' }, + { name: 'minDamage', type: 'uint256' }, + { name: 'minLevel', type: 'uint256' }, + { name: 'strModifier', type: 'int256' }, + ], + }, + ], + statsBytes as `0x${string}`, + )[0]; + + return { + agiModifier: itemTemplateStats.agiModifier.toString(), + classRestrictions: itemTemplateStats.classRestrictions.map( + (classRestriction: number) => classRestriction as StatsClasses, + ), + hitPointModifier: itemTemplateStats.hitPointModifier.toString(), + intModifier: itemTemplateStats.intModifier.toString(), + maxDamage: itemTemplateStats.maxDamage.toString(), + minDamage: itemTemplateStats.minDamage.toString(), + minLevel: itemTemplateStats.minLevel.toString(), + strModifier: itemTemplateStats.strModifier.toString(), + }; +}; + export const fetchMetadataFromUri = async (uri: string): Promise => { const res = await fetch(uri); if (!res.ok) throw new Error('Failed to fetch'); From 6b9acac6c6f97b04b87bc5af312cd8734e14a5e7 Mon Sep 17 00:00:00 2001 From: ECWireless Date: Mon, 5 Aug 2024 08:12:01 -0600 Subject: [PATCH 2/2] Tweak starter item modifiers --- packages/contracts/items.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/contracts/items.json b/packages/contracts/items.json index dafcf3636..c6e8bd8f3 100644 --- a/packages/contracts/items.json +++ b/packages/contracts/items.json @@ -24,14 +24,14 @@ "metadataUri": "2_rusty_sword.json", "name": "Rusty Sword", "stats": { - "agiModifier": 4, + "agiModifier": 2, "classRestrictions": [0], "hitPointModifier": 6, - "intModifier": 5, + "intModifier": 1, "maxDamage": 2, "minDamage": 1, "minLevel": 1, - "strModifier": 3 + "strModifier": 5 } }, { @@ -43,11 +43,11 @@ "agiModifier": 4, "classRestrictions": [1], "hitPointModifier": 6, - "intModifier": 5, + "intModifier": 2, "maxDamage": 2, "minDamage": 1, "minLevel": 1, - "strModifier": 3 + "strModifier": 2 } }, { @@ -56,14 +56,14 @@ "metadataUri": "4_cobbled_wand.json", "name": "Cobbled Wand", "stats": { - "agiModifier": 4, + "agiModifier": 2, "classRestrictions": [2], "hitPointModifier": 6, "intModifier": 5, "maxDamage": 2, "minDamage": 1, "minLevel": 1, - "strModifier": 3 + "strModifier": 1 } } ]