From f0b18290b7912e9c55d8551db8e54cf4217eab3a Mon Sep 17 00:00:00 2001
From: ECWireless
Date: Mon, 26 Aug 2024 14:55:52 -0600
Subject: [PATCH 1/3] Fix bug character creation game board redirect
---
packages/client/src/pages/CharacterCreation.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/client/src/pages/CharacterCreation.tsx b/packages/client/src/pages/CharacterCreation.tsx
index adfd39715..065864109 100644
--- a/packages/client/src/pages/CharacterCreation.tsx
+++ b/packages/client/src/pages/CharacterCreation.tsx
@@ -386,7 +386,6 @@ export const CharacterCreation = (): JSX.Element => {
if (character && rolledOnce) {
setCharacterClass(character.entityClass);
- return;
}
if (character?.locked) {
From 94c3fe13df94609e66d8ee3fe05d869c360bd098 Mon Sep 17 00:00:00 2001
From: ECWireless
Date: Mon, 26 Aug 2024 20:25:58 -0600
Subject: [PATCH 2/3] Add ItemsContext
---
.../client/src/contexts/CharacterContext.tsx | 137 +++--------
packages/client/src/contexts/ItemsContext.tsx | 227 ++++++++++++++++++
packages/client/src/index.tsx | 9 +-
packages/client/src/pages/AuctionHouse.tsx | 20 +-
4 files changed, 272 insertions(+), 121 deletions(-)
create mode 100644 packages/client/src/contexts/ItemsContext.tsx
diff --git a/packages/client/src/contexts/CharacterContext.tsx b/packages/client/src/contexts/CharacterContext.tsx
index 08acbf3a3..b9db83dcb 100644
--- a/packages/client/src/contexts/CharacterContext.tsx
+++ b/packages/client/src/contexts/CharacterContext.tsx
@@ -4,7 +4,7 @@ import {
HasValue,
runQuery,
} from '@latticexyz/recs';
-import { encodeEntity, singletonEntity } from '@latticexyz/store-sync/recs';
+import { encodeEntity } from '@latticexyz/store-sync/recs';
import {
createContext,
ReactNode,
@@ -16,12 +16,7 @@ import {
import { formatEther, hexToString, zeroHash } from 'viem';
import { useToast } from '../hooks/useToast';
-import {
- decodeArmorStats,
- decodeWeaponStats,
- fetchMetadataFromUri,
- uriToHttp,
-} from '../utils/helpers';
+import { fetchMetadataFromUri, uriToHttp } from '../utils/helpers';
import type {
Armor,
Character,
@@ -29,6 +24,7 @@ import type {
EntityStats,
Weapon,
} from '../utils/types';
+import { useItems } from './ItemsContext';
import { useMUD } from './MUDContext';
type CharacterContextType = {
@@ -61,10 +57,7 @@ export const CharacterProvider = ({
CharactersTokenURI,
EncounterEntity,
GoldBalances,
- Items,
- ItemsBaseURI,
ItemsOwners,
- ItemsTokenURI,
Stats,
},
delegatorAddress,
@@ -72,6 +65,7 @@ export const CharacterProvider = ({
network: { publicClient, worldContract },
} = useMUD();
const { renderError } = useToast();
+ const { armor, isLoading: isLoadingItems, weapons } = useItems();
const [userCharacter, setUserCharacter] = useState(null);
const [isRefreshing, setIsRefreshing] = useState(true);
@@ -209,12 +203,16 @@ export const CharacterProvider = ({
tokenOwnersEntity,
);
+ const armorDetails = armor.find(
+ item => item.tokenId === tokenId.toString(),
+ );
+
return {
+ ...armorDetails,
balance: itemOwner.balance.toString(),
itemId: tokenOwnersEntity,
owner: _character.owner,
- tokenId: tokenId.toString(),
- };
+ } as Armor;
});
const _weapons = _equippedWeapons
@@ -231,113 +229,25 @@ export const CharacterProvider = ({
tokenOwnersEntity,
);
+ const weaponDetails = weapons.find(
+ item => item.tokenId === tokenId.toString(),
+ );
+
return {
+ ...weaponDetails,
balance: itemOwner.balance.toString(),
itemId: tokenOwnersEntity,
owner: _character.owner,
tokenId: tokenId.toString(),
- };
+ } as Weapon;
})
.filter(item => item.owner === _character.owner)
.sort((a, b) => {
return Number(a.tokenId) - Number(b.tokenId);
});
- const fullArmor = await Promise.all(
- _armor.map(async item => {
- const tokenIdEntity = encodeEntity(
- { tokenId: 'uint256' },
- { tokenId: BigInt(item.tokenId) },
- );
-
- const itemTemplate = getComponentValueStrict(Items, tokenIdEntity);
- const decodedArmorStats = decodeArmorStats(itemTemplate.stats);
-
- const baseURI = getComponentValueStrict(
- ItemsBaseURI,
- singletonEntity,
- ).uri;
-
- const tokenURI = getComponentValueStrict(
- ItemsTokenURI,
- tokenIdEntity,
- ).uri;
-
- const metadata = await fetchMetadataFromUri(
- uriToHttp(`${baseURI}${tokenURI}`)[0],
- );
-
- return {
- ...metadata,
- agiModifier: decodedArmorStats.agiModifier,
- armorModifier: decodedArmorStats.armorModifier,
- balance: item.balance,
- hitPointModifier: decodedArmorStats.hitPointModifier,
- intModifier: decodedArmorStats.intModifier,
- itemId: item.itemId,
- minLevel: decodedArmorStats.minLevel,
- owner: item.owner,
- statRestrictions: {
- minAgility: decodedArmorStats.statRestrictions.minAgility,
- minIntelligence:
- decodedArmorStats.statRestrictions.minIntelligence,
- minStrength: decodedArmorStats.statRestrictions.minStrength,
- },
- strModifier: decodedArmorStats.strModifier,
- tokenId: item.tokenId,
- } as Armor;
- }),
- );
-
- const fullWeapons = await Promise.all(
- _weapons.map(async item => {
- const tokenIdEntity = encodeEntity(
- { tokenId: 'uint256' },
- { tokenId: BigInt(item.tokenId) },
- );
-
- const itemTemplate = getComponentValueStrict(Items, tokenIdEntity);
- const decodedWeaponStats = decodeWeaponStats(itemTemplate.stats);
-
- const baseURI = getComponentValueStrict(
- ItemsBaseURI,
- singletonEntity,
- ).uri;
-
- const tokenURI = getComponentValueStrict(
- ItemsTokenURI,
- tokenIdEntity,
- ).uri;
-
- const metadata = await fetchMetadataFromUri(
- uriToHttp(`${baseURI}${tokenURI}`)[0],
- );
-
- return {
- ...metadata,
- agiModifier: decodedWeaponStats.agiModifier,
- balance: item.balance,
- hitPointModifier: decodedWeaponStats.hitPointModifier,
- intModifier: decodedWeaponStats.intModifier,
- itemId: item.itemId,
- maxDamage: decodedWeaponStats.maxDamage,
- minDamage: decodedWeaponStats.minDamage,
- minLevel: decodedWeaponStats.minLevel,
- owner: item.owner,
- statRestrictions: {
- minAgility: decodedWeaponStats.statRestrictions.minAgility,
- minIntelligence:
- decodedWeaponStats.statRestrictions.minIntelligence,
- minStrength: decodedWeaponStats.statRestrictions.minStrength,
- },
- strModifier: decodedWeaponStats.strModifier,
- tokenId: item.tokenId,
- } as Weapon;
- }),
- );
-
- setEquippedArmor(fullArmor);
- setEquippedWeapons(fullWeapons);
+ setEquippedArmor(_armor);
+ setEquippedWeapons(_weapons);
} catch (e) {
renderError(
(e as Error)?.message ?? 'Failed to fetch character data.',
@@ -345,11 +255,12 @@ export const CharacterProvider = ({
);
}
},
- [Items, ItemsBaseURI, ItemsOwners, ItemsTokenURI, renderError],
+ [armor, ItemsOwners, renderError, weapons],
);
useEffect(() => {
if (!isSynced) return;
+ if (isLoadingItems) return;
(async (): Promise => {
if (!userCharacter) return;
@@ -361,7 +272,13 @@ export const CharacterProvider = ({
});
await fetchCharacterItems(userCharacter, equippedArmor, equippedWeapons);
})();
- }, [userCharacter, CharacterEquipment, fetchCharacterItems, isSynced]);
+ }, [
+ CharacterEquipment,
+ fetchCharacterItems,
+ isLoadingItems,
+ isSynced,
+ userCharacter,
+ ]);
return (
({
+ armor: [],
+ weapons: [],
+ isLoading: false,
+});
+
+export const ItemsProvider = ({
+ children,
+}: {
+ children: ReactNode;
+}): JSX.Element => {
+ const { renderError } = useToast();
+ const {
+ components: { Items, ItemsBaseURI, ItemsTokenURI },
+ isSynced,
+ } = useMUD();
+
+ const [armor, setArmor] = useState([]);
+ const [weapons, setWeapons] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+
+ const fetchAllArmor = useCallback(
+ async (allArmorIds: bigint[]) => {
+ const fullArmor = await Promise.all(
+ allArmorIds.map(async armorId => {
+ const tokenOwnersEntity = encodeEntity(
+ { owner: 'address', tokenId: 'uint256' },
+ {
+ owner: zeroAddress,
+ tokenId: armorId,
+ },
+ );
+
+ const tokenIdEntity = encodeEntity(
+ { tokenId: 'uint256' },
+ { tokenId: armorId },
+ );
+
+ const itemTemplate = getComponentValueStrict(Items, tokenIdEntity);
+ const decodedArmorStats = decodeArmorStats(itemTemplate.stats);
+
+ const baseURI = getComponentValueStrict(
+ ItemsBaseURI,
+ singletonEntity,
+ ).uri;
+
+ const tokenURI = getComponentValueStrict(
+ ItemsTokenURI,
+ tokenIdEntity,
+ ).uri;
+
+ const metadata = await fetchMetadataFromUri(
+ uriToHttp(`${baseURI}${tokenURI}`)[0],
+ );
+
+ return {
+ ...metadata,
+ agiModifier: decodedArmorStats.agiModifier,
+ armorModifier: decodedArmorStats.armorModifier,
+ balance: '0',
+ hitPointModifier: decodedArmorStats.hitPointModifier,
+ intModifier: decodedArmorStats.intModifier,
+ itemId: tokenOwnersEntity,
+ minLevel: decodedArmorStats.minLevel,
+ owner: zeroAddress,
+ statRestrictions: {
+ minAgility: decodedArmorStats.statRestrictions.minAgility,
+ minIntelligence:
+ decodedArmorStats.statRestrictions.minIntelligence,
+ minStrength: decodedArmorStats.statRestrictions.minStrength,
+ },
+ strModifier: decodedArmorStats.strModifier,
+ tokenId: armorId.toString(),
+ } as Armor;
+ }),
+ );
+
+ return fullArmor;
+ },
+ [Items, ItemsBaseURI, ItemsTokenURI],
+ );
+
+ const fetchAllWeapons = useCallback(
+ async (allWeaponIds: bigint[]) => {
+ const fullWeapons = await Promise.all(
+ allWeaponIds.map(async weaponId => {
+ const tokenOwnersEntity = encodeEntity(
+ { owner: 'address', tokenId: 'uint256' },
+ {
+ owner: zeroAddress,
+ tokenId: weaponId,
+ },
+ );
+
+ const tokenIdEntity = encodeEntity(
+ { tokenId: 'uint256' },
+ { tokenId: weaponId },
+ );
+
+ const itemTemplate = getComponentValueStrict(Items, tokenIdEntity);
+ const decodedArmorStats = decodeWeaponStats(itemTemplate.stats);
+
+ const baseURI = getComponentValueStrict(
+ ItemsBaseURI,
+ singletonEntity,
+ ).uri;
+
+ const tokenURI = getComponentValueStrict(
+ ItemsTokenURI,
+ tokenIdEntity,
+ ).uri;
+
+ const metadata = await fetchMetadataFromUri(
+ uriToHttp(`${baseURI}${tokenURI}`)[0],
+ );
+
+ return {
+ ...metadata,
+ agiModifier: decodedArmorStats.agiModifier,
+ balance: '0',
+ hitPointModifier: decodedArmorStats.hitPointModifier,
+ intModifier: decodedArmorStats.intModifier,
+ itemId: tokenOwnersEntity,
+ maxDamage: decodedArmorStats.maxDamage,
+ minDamage: decodedArmorStats.minDamage,
+ minLevel: decodedArmorStats.minLevel,
+ owner: zeroAddress,
+ statRestrictions: {
+ minAgility: decodedArmorStats.statRestrictions.minAgility,
+ minIntelligence:
+ decodedArmorStats.statRestrictions.minIntelligence,
+ minStrength: decodedArmorStats.statRestrictions.minStrength,
+ },
+ strModifier: decodedArmorStats.strModifier,
+ tokenId: weaponId.toString(),
+ } as Weapon;
+ }),
+ );
+
+ return fullWeapons;
+ },
+ [Items, ItemsBaseURI, ItemsTokenURI],
+ );
+
+ useEffect(() => {
+ (async () => {
+ if (!isSynced) return;
+
+ try {
+ const allItemIds = Array.from(runQuery([Has(Items)])).map(entity => {
+ const itemTemplate = getComponentValueStrict(Items, entity);
+ const { tokenId } = decodeEntity({ tokenId: 'uint256' }, entity);
+ return {
+ itemType: itemTemplate.itemType,
+ tokenId,
+ };
+ });
+
+ if (allItemIds.length > 0) {
+ const allArmorIds = allItemIds
+ .filter(({ itemType }) => itemType === ItemType.Armor)
+ .map(({ tokenId }) => tokenId);
+
+ const _armor = await fetchAllArmor(allArmorIds);
+ setArmor(_armor);
+
+ const allWeaponIds = allItemIds
+ .filter(({ itemType }) => itemType === ItemType.Weapon)
+ .map(({ tokenId }) => tokenId);
+
+ const _weapons = await fetchAllWeapons(allWeaponIds);
+ setWeapons(_weapons);
+ }
+ } catch (e) {
+ renderError((e as Error)?.message ?? 'Failed to fetch items.', e);
+ } finally {
+ setIsLoading(false);
+ }
+ })();
+ }, [fetchAllArmor, fetchAllWeapons, isSynced, Items, renderError]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useItems = (): ItemsContextType => useContext(ItemsContext);
diff --git a/packages/client/src/index.tsx b/packages/client/src/index.tsx
index e4ea6f209..c72e546ce 100644
--- a/packages/client/src/index.tsx
+++ b/packages/client/src/index.tsx
@@ -14,6 +14,7 @@ import { createRoot } from 'react-dom/client';
import { App } from './App';
import { DevTools } from './components/DevTools';
import { CharacterProvider } from './contexts/CharacterContext';
+import { ItemsProvider } from './contexts/ItemsContext';
import { MUDProvider } from './contexts/MUDContext';
import { Web3Provider } from './contexts/Web3Provider';
import { setup } from './lib/mud/setup';
@@ -30,9 +31,11 @@ setup().then(async result => {
-
-
-
+
+
+
+
+
{import.meta.env.DEV && }
diff --git a/packages/client/src/pages/AuctionHouse.tsx b/packages/client/src/pages/AuctionHouse.tsx
index c018c343d..85e2393c6 100644
--- a/packages/client/src/pages/AuctionHouse.tsx
+++ b/packages/client/src/pages/AuctionHouse.tsx
@@ -240,10 +240,6 @@ export const AuctionHouse = (): JSX.Element => {
: data.class;
data.stats = {
agiModifier: w.agiModifier.toString(),
- classRestrictions: w.classRestrictions.map(
- (classRestriction: number) =>
- classRestriction as StatsClasses,
- ),
hitPointModifier: w.hitPointModifier.toString(),
intModifier: w.intModifier.toString(),
itemId: item.itemId,
@@ -251,6 +247,12 @@ export const AuctionHouse = (): JSX.Element => {
minDamage: w.minDamage.toString(),
minLevel: w.minLevel.toString(),
owner: item.owner,
+ statRestrictions: {
+ minAgility: w.statRestrictions.minAgility.toString(),
+ minIntelligence:
+ w.statRestrictions.minIntelligence.toString(),
+ minStrength: w.statRestrictions.minStrength.toString(),
+ },
strModifier: w.strModifier.toString(),
tokenId: item.tokenId,
} as WeaponStats;
@@ -283,15 +285,17 @@ export const AuctionHouse = (): JSX.Element => {
data.stats = {
armorModifier: a.armorModifier.toString(),
agiModifier: a.agiModifier.toString(),
- classRestrictions: a.classRestrictions.map(
- (classRestriction: number) =>
- classRestriction as StatsClasses,
- ),
hitPointModifier: a.hitPointModifier.toString(),
intModifier: a.intModifier.toString(),
itemId: item.itemId,
minLevel: a.minLevel.toString(),
owner: item.owner,
+ statRestrictions: {
+ minAgility: a.statRestrictions.minAgility.toString(),
+ minIntelligence:
+ a.statRestrictions.minIntelligence.toString(),
+ minStrength: a.statRestrictions.minStrength.toString(),
+ },
strModifier: a.strModifier.toString(),
tokenId: item.tokenId,
} as ArmorStats;
From edbac41e5c4a958bb779a915e95e4ca89666e8e5 Mon Sep 17 00:00:00 2001
From: ECWireless
Date: Mon, 26 Aug 2024 22:19:48 -0600
Subject: [PATCH 3/3] Use ItemsContext throughout app
---
.../client/src/contexts/CharacterContext.tsx | 37 ++--
packages/client/src/contexts/ItemsContext.tsx | 20 +-
packages/client/src/pages/Character.tsx | 175 +++++-------------
.../client/src/pages/CharacterCreation.tsx | 145 +++------------
4 files changed, 102 insertions(+), 275 deletions(-)
diff --git a/packages/client/src/contexts/CharacterContext.tsx b/packages/client/src/contexts/CharacterContext.tsx
index b9db83dcb..fbfd6a1d4 100644
--- a/packages/client/src/contexts/CharacterContext.tsx
+++ b/packages/client/src/contexts/CharacterContext.tsx
@@ -65,7 +65,11 @@ export const CharacterProvider = ({
network: { publicClient, worldContract },
} = useMUD();
const { renderError } = useToast();
- const { armor, isLoading: isLoadingItems, weapons } = useItems();
+ const {
+ armorTemplates,
+ isLoading: isLoadingItemTemplates,
+ weaponTemplates,
+ } = useItems();
const [userCharacter, setUserCharacter] = useState(null);
const [isRefreshing, setIsRefreshing] = useState(true);
@@ -173,7 +177,7 @@ export const CharacterProvider = ({
]);
const fetchCharacterItems = useCallback(
- async (
+ (
_character: Character,
_equippedArmor: bigint[],
_equippedWeapons: bigint[],
@@ -203,7 +207,7 @@ export const CharacterProvider = ({
tokenOwnersEntity,
);
- const armorDetails = armor.find(
+ const armorDetails = armorTemplates.find(
item => item.tokenId === tokenId.toString(),
);
@@ -229,7 +233,7 @@ export const CharacterProvider = ({
tokenOwnersEntity,
);
- const weaponDetails = weapons.find(
+ const weaponDetails = weaponTemplates.find(
item => item.tokenId === tokenId.toString(),
);
@@ -255,27 +259,24 @@ export const CharacterProvider = ({
);
}
},
- [armor, ItemsOwners, renderError, weapons],
+ [armorTemplates, ItemsOwners, renderError, weaponTemplates],
);
useEffect(() => {
- if (!isSynced) return;
- if (isLoadingItems) return;
- (async (): Promise => {
- if (!userCharacter) return;
+ if (!(isSynced && userCharacter) || isLoadingItemTemplates) return;
- const { equippedArmor, equippedWeapons } =
- getComponentValue(CharacterEquipment, userCharacter.id) ??
- ({ equippedArmor: [], equippedWeapons: [] } as {
- equippedArmor: bigint[];
- equippedWeapons: bigint[];
- });
- await fetchCharacterItems(userCharacter, equippedArmor, equippedWeapons);
- })();
+ const { equippedArmor, equippedWeapons } =
+ getComponentValue(CharacterEquipment, userCharacter.id) ??
+ ({ equippedArmor: [], equippedWeapons: [] } as {
+ equippedArmor: bigint[];
+ equippedWeapons: bigint[];
+ });
+
+ fetchCharacterItems(userCharacter, equippedArmor, equippedWeapons);
}, [
CharacterEquipment,
fetchCharacterItems,
- isLoadingItems,
+ isLoadingItemTemplates,
isSynced,
userCharacter,
]);
diff --git a/packages/client/src/contexts/ItemsContext.tsx b/packages/client/src/contexts/ItemsContext.tsx
index 5e458dcb7..67ad9649b 100644
--- a/packages/client/src/contexts/ItemsContext.tsx
+++ b/packages/client/src/contexts/ItemsContext.tsx
@@ -25,14 +25,14 @@ import { type Armor, ItemType, type Weapon } from '../utils/types';
import { useMUD } from './MUDContext';
type ItemsContextType = {
- armor: Armor[];
- weapons: Weapon[];
+ armorTemplates: Armor[];
+ weaponTemplates: Weapon[];
isLoading: boolean;
};
const ItemsContext = createContext({
- armor: [],
- weapons: [],
+ armorTemplates: [],
+ weaponTemplates: [],
isLoading: false,
});
@@ -47,8 +47,8 @@ export const ItemsProvider = ({
isSynced,
} = useMUD();
- const [armor, setArmor] = useState([]);
- const [weapons, setWeapons] = useState([]);
+ const [armorTemplates, setArmorTemplates] = useState([]);
+ const [weaponTemplates, setWeaponTemplates] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const fetchAllArmor = useCallback(
@@ -194,14 +194,14 @@ export const ItemsProvider = ({
.map(({ tokenId }) => tokenId);
const _armor = await fetchAllArmor(allArmorIds);
- setArmor(_armor);
+ setArmorTemplates(_armor);
const allWeaponIds = allItemIds
.filter(({ itemType }) => itemType === ItemType.Weapon)
.map(({ tokenId }) => tokenId);
const _weapons = await fetchAllWeapons(allWeaponIds);
- setWeapons(_weapons);
+ setWeaponTemplates(_weapons);
}
} catch (e) {
renderError((e as Error)?.message ?? 'Failed to fetch items.', e);
@@ -214,8 +214,8 @@ export const ItemsProvider = ({
return (
diff --git a/packages/client/src/pages/Character.tsx b/packages/client/src/pages/Character.tsx
index 5777c1944..4fbda9ef3 100644
--- a/packages/client/src/pages/Character.tsx
+++ b/packages/client/src/pages/Character.tsx
@@ -20,14 +20,8 @@ import {
Entity,
getComponentValue,
getComponentValueStrict,
- Has,
- runQuery,
} from '@latticexyz/recs';
-import {
- decodeEntity,
- encodeEntity,
- singletonEntity,
-} from '@latticexyz/store-sync/recs';
+import { encodeEntity } from '@latticexyz/store-sync/recs';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { FaHatWizard } from 'react-icons/fa';
import { GiAxeSword, GiRogue } from 'react-icons/gi';
@@ -41,21 +35,19 @@ import { ItemEquipModal } from '../components/ItemEquipModal';
import { Level } from '../components/Level';
import { LevelingPanel } from '../components/LevelingPanel';
import { useCharacter } from '../contexts/CharacterContext';
+import { useItems } from '../contexts/ItemsContext';
import { useMUD } from '../contexts/MUDContext';
import { useToast } from '../hooks/useToast';
import { AUCTION_HOUSE_PATH, 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';
import {
type Armor,
type Character,
- ItemType,
StatsClasses,
type Weapon,
} from '../utils/types';
@@ -445,14 +437,13 @@ const ItemsPanel = ({ character }: { character: Character }): JSX.Element => {
const { renderError } = useToast();
const {
- components: {
- CharacterEquipment,
- Items,
- ItemsBaseURI,
- ItemsOwners,
- ItemsTokenURI,
- },
+ components: { CharacterEquipment, ItemsOwners },
} = useMUD();
+ const {
+ armorTemplates,
+ isLoading: isLoadingItemTemplates,
+ weaponTemplates,
+ } = useItems();
const {
isOpen: isItemModalOpen,
@@ -476,129 +467,52 @@ const ItemsPanel = ({ character }: { character: Character }): JSX.Element => {
const maxWeaponsEquipped = equippedWeapons.length === MAX_EQUIPPED_WEAPONS;
const fetchCharacterItems = useCallback(
- async (_character: Character) => {
+ (_character: Character) => {
try {
- const _items = Array.from(runQuery([Has(ItemsOwners)]))
- .map(entity => {
- const itemdBalance = getComponentValueStrict(
- ItemsOwners,
- entity,
- ).balance;
-
- const { owner, tokenId } = decodeEntity(
+ const _armor = armorTemplates
+ .map(armor => {
+ const tokenOwnersEntity = encodeEntity(
{ owner: 'address', tokenId: 'uint256' },
- entity,
- );
-
- const tokenIdEntity = encodeEntity(
- { tokenId: 'uint256' },
- { tokenId },
+ {
+ owner: _character.owner as `0x${string}`,
+ tokenId: BigInt(armor.tokenId),
+ },
);
- const itemTemplate = getComponentValueStrict(Items, tokenIdEntity);
+ const itemOwner = getComponentValue(ItemsOwners, tokenOwnersEntity);
return {
- balance: itemdBalance.toString(),
- itemId: entity,
- itemType: itemTemplate.itemType,
- owner,
- stats: itemTemplate.stats,
- tokenId: tokenId.toString(),
- tokenIdEntity,
- };
+ ...armor,
+ balance: itemOwner ? itemOwner.balance.toString() : '0',
+ itemId: tokenOwnersEntity,
+ owner: _character.owner,
+ } as Armor;
})
- .filter(item => item.owner === _character.owner)
- .sort((a, b) => {
- return Number(a.tokenId) - Number(b.tokenId);
- });
-
- const _armor = _items.filter(item => item.itemType === ItemType.Armor);
- const _weapons = _items.filter(
- item => item.itemType === ItemType.Weapon,
- );
-
- const fullArmor = await Promise.all(
- _armor.map(async item => {
- const decodedArmorStats = decodeArmorStats(item.stats);
-
- const baseURI = getComponentValueStrict(
- ItemsBaseURI,
- singletonEntity,
- ).uri;
-
- const tokenURI = getComponentValueStrict(
- ItemsTokenURI,
- item.tokenIdEntity,
- ).uri;
-
- const metadata = await fetchMetadataFromUri(
- uriToHttp(`${baseURI}${tokenURI}`)[0],
- );
+ .filter(a => a.balance !== '0');
- return {
- ...metadata,
- agiModifier: decodedArmorStats.agiModifier,
- armorModifier: decodedArmorStats.armorModifier,
- balance: item.balance,
- hitPointModifier: decodedArmorStats.hitPointModifier,
- intModifier: decodedArmorStats.intModifier,
- itemId: item.itemId,
- owner: item.owner,
- statRestrictions: {
- minAgility: decodedArmorStats.statRestrictions.minAgility,
- minIntelligence:
- decodedArmorStats.statRestrictions.minIntelligence,
- minStrength: decodedArmorStats.statRestrictions.minStrength,
+ const _weapons = weaponTemplates
+ .map(weapon => {
+ const tokenOwnersEntity = encodeEntity(
+ { owner: 'address', tokenId: 'uint256' },
+ {
+ owner: _character.owner as `0x${string}`,
+ tokenId: BigInt(weapon.tokenId),
},
- strModifier: decodedArmorStats.strModifier,
- tokenId: item.tokenId,
- } as Armor;
- }),
- );
-
- const fullWeapons = await Promise.all(
- _weapons.map(async item => {
- const decodedWeaponStats = decodeWeaponStats(item.stats);
-
- const baseURI = getComponentValueStrict(
- ItemsBaseURI,
- singletonEntity,
- ).uri;
-
- const tokenURI = getComponentValueStrict(
- ItemsTokenURI,
- item.tokenIdEntity,
- ).uri;
-
- const metadata = await fetchMetadataFromUri(
- uriToHttp(`${baseURI}${tokenURI}`)[0],
);
+ const itemOwner = getComponentValue(ItemsOwners, tokenOwnersEntity);
+
return {
- ...metadata,
- agiModifier: decodedWeaponStats.agiModifier,
- balance: item.balance,
- hitPointModifier: decodedWeaponStats.hitPointModifier,
- intModifier: decodedWeaponStats.intModifier,
- itemId: item.itemId,
- maxDamage: decodedWeaponStats.maxDamage,
- minDamage: decodedWeaponStats.minDamage,
- minLevel: decodedWeaponStats.minLevel,
- owner: item.owner,
- statRestrictions: {
- minAgility: decodedWeaponStats.statRestrictions.minAgility,
- minIntelligence:
- decodedWeaponStats.statRestrictions.minIntelligence,
- minStrength: decodedWeaponStats.statRestrictions.minStrength,
- },
- strModifier: decodedWeaponStats.strModifier,
- tokenId: item.tokenId,
+ ...weapon,
+ balance: itemOwner ? itemOwner.balance.toString() : '0',
+ itemId: tokenOwnersEntity,
+ owner: _character.owner,
} as Weapon;
- }),
- );
+ })
+ .filter(w => w.balance !== '0');
- setArmor(fullArmor);
- setWeapons(fullWeapons);
+ setArmor(_armor);
+ setWeapons(_weapons);
} catch (e) {
renderError(
(e as Error)?.message ?? 'Failed to fetch character items.',
@@ -608,14 +522,13 @@ const ItemsPanel = ({ character }: { character: Character }): JSX.Element => {
setIsLoadingItems(false);
}
},
- [Items, ItemsBaseURI, ItemsOwners, ItemsTokenURI, renderError],
+ [armorTemplates, ItemsOwners, renderError, weaponTemplates],
);
useEffect(() => {
- (async (): Promise => {
- await fetchCharacterItems(character);
- })();
- }, [character, fetchCharacterItems]);
+ if (isLoadingItemTemplates) return;
+ fetchCharacterItems(character);
+ }, [character, fetchCharacterItems, isLoadingItemTemplates]);
if (isLoadingItems) {
return (
diff --git a/packages/client/src/pages/CharacterCreation.tsx b/packages/client/src/pages/CharacterCreation.tsx
index 065864109..9c5a26a6b 100644
--- a/packages/client/src/pages/CharacterCreation.tsx
+++ b/packages/client/src/pages/CharacterCreation.tsx
@@ -18,7 +18,7 @@ import {
} from '@chakra-ui/react';
import { useComponentValue } from '@latticexyz/react';
import { getComponentValueStrict, Has, runQuery } from '@latticexyz/recs';
-import { encodeEntity, singletonEntity } from '@latticexyz/store-sync/recs';
+import { singletonEntity } from '@latticexyz/store-sync/recs';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { FaLock } from 'react-icons/fa';
import { useNavigate } from 'react-router-dom';
@@ -26,18 +26,13 @@ import { useAccount } from 'wagmi';
import { ItemCardSmall } from '../components/ItemCard';
import { useCharacter } from '../contexts/CharacterContext';
+import { useItems } from '../contexts/ItemsContext';
import { useMUD } from '../contexts/MUDContext';
import { useToast } from '../hooks/useToast';
import { useUploadFile } from '../hooks/useUploadFile';
import { GAME_BOARD_PATH, HOME_PATH } from '../Routes';
import { API_URL } from '../utils/constants';
-import {
- decodeArmorStats,
- decodeWeaponStats,
- fetchMetadataFromUri,
- shortenAddress,
- uriToHttp,
-} from '../utils/helpers';
+import { shortenAddress } from '../utils/helpers';
import { type Armor, StatsClasses, type Weapon } from '../utils/types';
export const CharacterCreation = (): JSX.Element => {
@@ -46,17 +41,16 @@ export const CharacterCreation = (): JSX.Element => {
const isSmallScreen = useBreakpointValue({ base: true, lg: false });
const { isConnected } = useAccount();
const {
- components: {
- Items,
- ItemsBaseURI,
- ItemsTokenURI,
- StarterItems,
- UltimateDominionConfig,
- },
+ components: { Items, StarterItems, UltimateDominionConfig },
delegatorAddress,
isSynced,
systemCalls: { enterGame, mintCharacter, rollStats },
} = useMUD();
+ const {
+ armorTemplates,
+ isLoading: isLoadingItemTemplates,
+ weaponTemplates,
+ } = useItems();
const { character, isRefreshing, refreshCharacter } = useCharacter();
const {
file: avatar,
@@ -88,103 +82,9 @@ export const CharacterCreation = (): JSX.Element => {
setShowError(false);
}, [avatar, description, name]);
- const fetchStarterItems = useCallback(
- async (starterArmorTokenIds: bigint[], starterWeaponTokenIds: bigint[]) => {
- try {
- const _armor: Armor[] = await Promise.all(
- starterArmorTokenIds.map(async tokenId => {
- const tokenIdEntity = encodeEntity(
- { tokenId: 'uint256' },
- { tokenId },
- );
-
- const itemTemplate = getComponentValueStrict(Items, tokenIdEntity);
- const decodedArmorStats = decodeArmorStats(itemTemplate.stats);
-
- const baseURI = getComponentValueStrict(
- ItemsBaseURI,
- singletonEntity,
- ).uri;
-
- const tokenURI = getComponentValueStrict(
- ItemsTokenURI,
- tokenIdEntity,
- ).uri;
-
- const fetachedMetadata = await fetchMetadataFromUri(
- uriToHttp(`${baseURI}${tokenURI}`)[0],
- );
-
- return {
- agiModifier: decodedArmorStats.agiModifier,
- armorModifier: decodedArmorStats.armorModifier,
- hitPointModifier: decodedArmorStats.hitPointModifier,
- intModifier: decodedArmorStats.intModifier,
- statRestrictions: {
- minAgility: decodedArmorStats.statRestrictions.minAgility,
- minIntelligence:
- decodedArmorStats.statRestrictions.minIntelligence,
- minStrength: decodedArmorStats.statRestrictions.minStrength,
- },
- strModifier: decodedArmorStats.strModifier,
- ...fetachedMetadata,
- } as Armor;
- }),
- );
-
- const _weapons: Weapon[] = await Promise.all(
- starterWeaponTokenIds.map(async tokenId => {
- const tokenIdEntity = encodeEntity(
- { tokenId: 'uint256' },
- { tokenId },
- );
-
- const itemTemplate = getComponentValueStrict(Items, tokenIdEntity);
- const decodedWeaponStats = decodeWeaponStats(itemTemplate.stats);
-
- const baseURI = getComponentValueStrict(
- ItemsBaseURI,
- singletonEntity,
- ).uri;
-
- const tokenURI = getComponentValueStrict(
- ItemsTokenURI,
- tokenIdEntity,
- ).uri;
-
- const fetachedMetadata = await fetchMetadataFromUri(
- uriToHttp(`${baseURI}${tokenURI}`)[0],
- );
-
- return {
- agiModifier: decodedWeaponStats.agiModifier,
- hitPointModifier: decodedWeaponStats.hitPointModifier,
- intModifier: decodedWeaponStats.intModifier,
- maxDamage: decodedWeaponStats.maxDamage,
- minDamage: decodedWeaponStats.minDamage,
- minLevel: decodedWeaponStats.minLevel,
- statRestrictions: {
- minAgility: decodedWeaponStats.statRestrictions.minAgility,
- minIntelligence:
- decodedWeaponStats.statRestrictions.minIntelligence,
- minStrength: decodedWeaponStats.statRestrictions.minStrength,
- },
- strModifier: decodedWeaponStats.strModifier,
- ...fetachedMetadata,
- } as Weapon;
- }),
- );
-
- setStarterArmor(_armor);
- setStarterWeapons(_weapons);
- } catch (e) {
- renderError((e as Error)?.message ?? 'Error fetching starter item.', e);
- }
- },
- [Items, ItemsBaseURI, ItemsTokenURI, renderError],
- );
-
useEffect(() => {
+ if (isLoadingItemTemplates) return;
+
const starterItemTokenIds = Array.from(runQuery([Has(StarterItems)])).map(
entity => {
const tokenIds = getComponentValueStrict(StarterItems, entity).itemIds;
@@ -193,15 +93,28 @@ export const CharacterCreation = (): JSX.Element => {
);
const starterArmorTokenIds = starterItemTokenIds
- .map(item => item[0] as bigint)
+ .map(item => item[0].toString())
.filter((value, index, self) => self.indexOf(value) === index);
+ const starterWeaponTokenIds = starterItemTokenIds.map(item =>
+ item[1].toString(),
+ );
- const starterWeaponTokenIds = starterItemTokenIds.map(
- item => item[1] as bigint,
+ const _starterArmor = armorTemplates.filter(armor =>
+ starterArmorTokenIds.includes(armor.tokenId),
+ );
+ const _starterWeapons = weaponTemplates.filter(weapon =>
+ starterWeaponTokenIds.includes(weapon.tokenId),
);
- fetchStarterItems(starterArmorTokenIds, starterWeaponTokenIds);
- }, [fetchStarterItems, Items, StarterItems]);
+ setStarterArmor(_starterArmor);
+ setStarterWeapons(_starterWeapons);
+ }, [
+ armorTemplates,
+ isLoadingItemTemplates,
+ Items,
+ StarterItems,
+ weaponTemplates,
+ ]);
const onUploadAvatar = useCallback(() => {
const input = document.getElementById('avatarInput');