diff --git a/client/apps/game/src/hooks/helpers/use-banks.tsx b/client/apps/game/src/hooks/helpers/use-bank.tsx similarity index 100% rename from client/apps/game/src/hooks/helpers/use-banks.tsx rename to client/apps/game/src/hooks/helpers/use-bank.tsx diff --git a/client/apps/game/src/hooks/helpers/use-battle-events.tsx b/client/apps/game/src/hooks/helpers/use-battle-events.tsx index 0f212418e..7fe098eef 100644 --- a/client/apps/game/src/hooks/helpers/use-battle-events.tsx +++ b/client/apps/game/src/hooks/helpers/use-battle-events.tsx @@ -1,22 +1,19 @@ import { useDojo } from "@/hooks/context/dojo-context"; import { BattleSide, ID } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; -import { getComponentValue, HasValue } from "@dojoengine/recs"; +import { Component, getComponentValue, HasValue } from "@dojoengine/recs"; import { useMemo } from "react"; -export const useBattleStart = (battleEntityId: ID) => { - const { - setup: { - components: { - events: { BattleStartData }, - }, - }, - } = useDojo(); - - const battleStartDataEntityIds = useEntityQuery( +const useBattleEventData = ( + eventComponent: Component, + battleEntityId: ID, + sideFilter?: { key: string; side: BattleSide }, +) => { + const eventEntityIds = useEntityQuery( [ - HasValue(BattleStartData, { + HasValue(eventComponent, { battle_entity_id: battleEntityId, + ...(sideFilter ? { [sideFilter.key]: BattleSide[sideFilter.side] } : {}), }), ], { @@ -24,14 +21,21 @@ export const useBattleStart = (battleEntityId: ID) => { }, ); - const battleStartData = useMemo(() => { - return battleStartDataEntityIds.map((entityId) => { - const battleStartData = getComponentValue(BattleStartData, entityId); - return battleStartData; - }); - }, [battleStartDataEntityIds]); + return useMemo(() => { + return eventEntityIds.map((entityId) => getComponentValue(eventComponent, entityId)); + }, [eventEntityIds]); +}; + +export const useBattleStart = (battleEntityId: ID) => { + const { + setup: { + components: { + events: { BattleStartData }, + }, + }, + } = useDojo(); - return battleStartData; + return useBattleEventData(BattleStartData, battleEntityId); }; export const useBattleJoin = (battleEntityId: ID, joinerSide?: BattleSide) => { @@ -43,27 +47,13 @@ export const useBattleJoin = (battleEntityId: ID, joinerSide?: BattleSide) => { }, } = useDojo(); - const battleStartDataEntityIds = useEntityQuery( - [ - HasValue(BattleJoinData, { - battle_entity_id: battleEntityId, - ...(joinerSide ? { joiner_side: BattleSide[joinerSide] } : {}), - }), - ], - { - updateOnValueChange: false, - }, + return useBattleEventData( + BattleJoinData, + battleEntityId, + joinerSide ? { key: "joiner_side", side: joinerSide } : undefined, ); - - const battleStartData = useMemo(() => { - return battleStartDataEntityIds.map((entityId) => { - const battleJoinData = getComponentValue(BattleJoinData, entityId); - return battleJoinData; - }); - }, [battleStartDataEntityIds]); - - return battleStartData; }; + export const useBattleLeave = (battleEntityId: ID, leaverSide?: BattleSide) => { const { setup: { @@ -73,24 +63,9 @@ export const useBattleLeave = (battleEntityId: ID, leaverSide?: BattleSide) => { }, } = useDojo(); - const battleLeaveDataEntityIds = useEntityQuery( - [ - HasValue(BattleLeaveData, { - battle_entity_id: battleEntityId, - ...(leaverSide ? { leaver_side: BattleSide[leaverSide] } : {}), - }), - ], - { - updateOnValueChange: false, - }, + return useBattleEventData( + BattleLeaveData, + battleEntityId, + leaverSide ? { key: "leaver_side", side: leaverSide } : undefined, ); - - const battleLeaveData = useMemo(() => { - return battleLeaveDataEntityIds.map((entityId) => { - const battleLeaveData = getComponentValue(BattleLeaveData, entityId); - return battleLeaveData; - }); - }, [battleLeaveDataEntityIds]); - - return battleLeaveData; }; diff --git a/client/apps/game/src/hooks/helpers/use-battles.tsx b/client/apps/game/src/hooks/helpers/use-battles.tsx index 9deae4058..ecad77e51 100644 --- a/client/apps/game/src/hooks/helpers/use-battles.tsx +++ b/client/apps/game/src/hooks/helpers/use-battles.tsx @@ -1,10 +1,10 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntities } from "@/hooks/helpers/use-entities"; -import { BattleManager, ID, Position } from "@bibliothecadao/eternum"; +import { BattleManager, ContractAddress, ID, Position } from "@bibliothecadao/eternum"; import { useComponentValue, useEntityQuery } from "@dojoengine/react"; import { Has, HasValue, NotValue, getComponentValue, runQuery } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { useMemo } from "react"; +import { usePlayerRealms } from "./use-entities"; export const useBattleManager = (battleEntityId: ID) => { const dojo = useDojo(); @@ -40,13 +40,13 @@ export const useBattlesAtPosition = ({ x, y }: Position) => { export const usePlayerBattles = () => { const { + account: { account }, setup: { components: { Army, EntityOwner }, }, } = useDojo(); - const { playerRealms } = useEntities(); - const realms = playerRealms(); + const realms = usePlayerRealms(ContractAddress(account.address)); const battleEntityIds = useMemo(() => { // Get all armies in battle owned by player's realms diff --git a/client/apps/game/src/hooks/helpers/use-buildings.tsx b/client/apps/game/src/hooks/helpers/use-buildings.tsx index b20781259..1eb8b2ff1 100644 --- a/client/apps/game/src/hooks/helpers/use-buildings.tsx +++ b/client/apps/game/src/hooks/helpers/use-buildings.tsx @@ -1,33 +1,24 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { ResourceIdToMiningType } from "@/ui/utils/utils"; -import { BuildingType, ResourceCost, ResourcesIds } from "@bibliothecadao/eternum"; -import { getComponentValue, Has, HasValue, runQuery } from "@dojoengine/recs"; +import { getEntityIdFromKeys, ResourceIdToMiningType } from "@/ui/utils/utils"; +import { Building, BuildingType, ID, ResourcesIds } from "@bibliothecadao/eternum"; +import { useEntityQuery } from "@dojoengine/react"; +import { getComponentValue, Has, HasValue } from "@dojoengine/recs"; +import { useMemo } from "react"; -export interface Building { - name: string; - category: string; - paused: boolean; - produced: ResourceCost; - consumed: ResourceCost[]; - bonusPercent: number; - innerCol: number; - innerRow: number; -} - -export const useBuildings = () => { +export const useBuildings = (outerCol: number, outerRow: number) => { const { setup: { components: { Building }, }, } = useDojo(); - const getBuildings = (outerCol: number, outerRow: number): Building[] => { - const buildingEntities = runQuery([ - Has(Building), - HasValue(Building, { outer_col: outerCol, outer_row: outerRow }), - ]); + const buildingEntities = useEntityQuery([ + Has(Building), + HasValue(Building, { outer_col: outerCol, outer_row: outerRow }), + ]); + const buildings = useMemo(() => { return Array.from(buildingEntities) .map((entity) => { const building = getComponentValue(Building, entity); @@ -60,7 +51,30 @@ export const useBuildings = () => { }; }) .filter((building) => building != null); - }; + }, [buildingEntities]); + + return buildings as Building[]; +}; + +export const useBuildingQuantities = (structureEntityId: ID | undefined) => { + const { + setup: { + components: { BuildingQuantityv2 }, + }, + } = useDojo(); + const entityUpdate = useEntityQuery([HasValue(BuildingQuantityv2, { entity_id: structureEntityId || 0 })]); + + const getBuildingQuantity = (buildingType: BuildingType) => + getComponentValue(BuildingQuantityv2, getEntityIdFromKeys([BigInt(structureEntityId || 0), BigInt(buildingType)])) + ?.value || 0; - return { getBuildings }; + return useMemo( + () => ({ + food: getBuildingQuantity(BuildingType.Farm) + getBuildingQuantity(BuildingType.FishingVillage), + resource: getBuildingQuantity(BuildingType.Resource), + workersHut: getBuildingQuantity(BuildingType.WorkersHut), + markets: getBuildingQuantity(BuildingType.Market), + }), + [structureEntityId, entityUpdate], + ); }; diff --git a/client/apps/game/src/hooks/helpers/use-contributions.tsx b/client/apps/game/src/hooks/helpers/use-contributions.tsx index 816ac9d7b..88474c44b 100644 --- a/client/apps/game/src/hooks/helpers/use-contributions.tsx +++ b/client/apps/game/src/hooks/helpers/use-contributions.tsx @@ -1,98 +1,20 @@ -import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { divideByPrecision } from "@/ui/utils/utils"; -import { ClientComponents, ContractAddress, ID, Resource } from "@bibliothecadao/eternum"; +import { ClientComponents, ContractAddress, ID } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; -import { ComponentValue, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; -import { useCallback } from "react"; +import { ComponentValue, HasValue, getComponentValue } from "@dojoengine/recs"; -export const useContributions = () => { +export const usePlayerContributions = (playerAddress: ContractAddress, hyperstructureEntityId: ID) => { const { setup: { components: { Contribution }, }, } = useDojo(); - const getContributions = (hyperstructureEntityId: ID) => { - const contributionsToHyperstructure = Array.from( - runQuery([HasValue(Contribution, { hyperstructure_entity_id: hyperstructureEntityId })]), - ).map((id) => getComponentValue(Contribution, id)); + const contributionsToHyperstructure = useEntityQuery([ + HasValue(Contribution, { hyperstructure_entity_id: hyperstructureEntityId, player_address: playerAddress }), + ]) + .map((id) => getComponentValue(Contribution, id)) + .filter((x): x is ComponentValue => x !== undefined); - return contributionsToHyperstructure as ComponentValue[]; - }; - - const useContributionsByPlayerAddress = (playerAddress: ContractAddress, hyperstructureEntityId: ID) => { - const contributionsToHyperstructure = useEntityQuery([ - HasValue(Contribution, { hyperstructure_entity_id: hyperstructureEntityId, player_address: playerAddress }), - ]) - .map((id) => getComponentValue(Contribution, id)) - .filter((x): x is ComponentValue => x !== undefined); - - return contributionsToHyperstructure; - }; - - const getContributionsTotalPercentage = (hyperstructureId: number, contributions: Resource[]) => { - const totalPlayerContribution = divideByPrecision( - contributions.reduce((acc, { amount, resourceId }) => { - return acc + amount * configManager.getResourceRarity(resourceId); - }, 0), - ); - - const totalHyperstructureContribution = configManager.getHyperstructureTotalContributableAmount(hyperstructureId); - - return totalPlayerContribution / totalHyperstructureContribution; - }; - - return { - getContributions, - useContributionsByPlayerAddress, - getContributionsTotalPercentage, - }; -}; - -export const useGetHyperstructuresWithContributionsFromPlayer = () => { - const { - account: { account }, - setup: { - components: { Contribution }, - }, - } = useDojo(); - - const getContributions = useCallback(() => { - const entityIds = runQuery([HasValue(Contribution, { player_address: ContractAddress(account.address) })]); - const hyperstructureEntityIds = Array.from(entityIds).map( - (entityId) => getComponentValue(Contribution, entityId)?.hyperstructure_entity_id ?? 0, - ); - return new Set(hyperstructureEntityIds); - }, [account.address]); - - return getContributions; -}; - -export const useGetUnregisteredContributions = () => { - const { - account: { account }, - setup: { - components: { LeaderboardRegisterContribution }, - }, - } = useDojo(); - const getContributions = useGetHyperstructuresWithContributionsFromPlayer(); - - const getUnregisteredContributions = useCallback(() => { - const registeredContributionsEntities = runQuery([ - HasValue(LeaderboardRegisterContribution, { address: ContractAddress(account.address) }), - ]); - const registeredContributions = Array.from(registeredContributionsEntities) - .map((entityId) => getComponentValue(LeaderboardRegisterContribution, entityId)?.hyperstructure_entity_id) - .filter((x): x is number => x !== undefined); - console.log("registeredContributions", registeredContributions); - const hyperstructuresContributedTo = Array.from(getContributions()); - console.log("hyperstructuresContributedTo", hyperstructuresContributedTo); - return hyperstructuresContributedTo.filter( - (hyperstructureEntityId) => - !registeredContributions.some((contribution) => contribution === hyperstructureEntityId), - ); - }, [getContributions]); - - return getUnregisteredContributions; + return contributionsToHyperstructure; }; diff --git a/client/apps/game/src/hooks/helpers/use-entities.tsx b/client/apps/game/src/hooks/helpers/use-entities.tsx index 3e9798cac..20952283b 100644 --- a/client/apps/game/src/hooks/helpers/use-entities.tsx +++ b/client/apps/game/src/hooks/helpers/use-entities.tsx @@ -1,235 +1,50 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useResourcesUtils } from "@/hooks/helpers/use-resources"; -import useUIStore from "@/hooks/store/use-ui-store"; -import { getRealmName, getRealmNameById } from "@/ui/utils/realms"; -import { divideByPrecision, getEntityIdFromKeys } from "@/ui/utils/utils"; -import { - CAPACITY_CONFIG_CATEGORY_STRING_MAP, - ContractAddress, - EntityType, - StructureType, - type ClientComponents, - type ID, -} from "@bibliothecadao/eternum"; +import { getRealm, getStructure } from "@/utils/entities"; +import { ContractAddress, PlayerStructure, RealmWithPosition } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; -import { Has, HasValue, getComponentValue, type ComponentValue } from "@dojoengine/recs"; +import { Has, HasValue } from "@dojoengine/recs"; import { useMemo } from "react"; -import { shortString } from "starknet"; -export type PlayerStructure = ComponentValue & { - position: ComponentValue; - name: string; - category?: string | undefined; - owner: ComponentValue; -}; - -export type RealmWithPosition = ComponentValue & { - position: ComponentValue; - name: string; - owner: ComponentValue; -}; - -export const useEntities = () => { +export const usePlayerRealms = (playerAddress?: ContractAddress) => { const { account: { account }, - setup: { - components: { Realm, Owner, Position, Structure }, - }, + setup: { components }, } = useDojo(); - const isSpectatorMode = useUIStore((state) => state.isSpectatorMode); - const address = isSpectatorMode ? ContractAddress("0x0") : ContractAddress(account.address); - - const { getEntityName } = useEntitiesUtils(); - - // Get all realms - const playerRealmsQuery = useEntityQuery([Has(Realm), HasValue(Owner, { address: address })]); - - // Get all structures - const playerStructuresQuery = useEntityQuery([ - Has(Structure), - Has(Position), - Has(Owner), - HasValue(Owner, { address: address }), + const entities = useEntityQuery([ + Has(components.Realm), + HasValue(components.Owner, { address: playerAddress || ContractAddress(account.address) }), ]); const playerRealms = useMemo(() => { - return playerRealmsQuery.map((id) => { - const realm = getComponentValue(Realm, id); - return { - ...realm, - position: getComponentValue(Position, id), - name: getRealmNameById(realm!.realm_id), - owner: getComponentValue(Owner, id), - } as RealmWithPosition; - }); - }, [playerRealmsQuery]); - - const playerStructures = useMemo(() => { - return playerStructuresQuery - .map((id) => { - const structure = getComponentValue(Structure, id); - if (!structure) return; - - const realm = getComponentValue(Realm, id); - const position = getComponentValue(Position, id); + return entities + .map((id) => getRealm(id, components)) + .filter((realm): realm is RealmWithPosition => realm !== undefined) + .sort((a, b) => a.name.localeCompare(b.name)); + }, [entities]); - const structureName = getEntityName(structure.entity_id); - - const name = realm ? getRealmName(realm) : structureName || structure.category || ""; - - return { ...structure, position: position!, name, owner: getComponentValue(Owner, id) }; - }) - .filter((structure): structure is PlayerStructure => structure !== undefined) - .sort((a, b) => { - if (a.category === StructureType[StructureType.Realm]) return -1; - if (b.category === StructureType[StructureType.Realm]) return 1; - return a.category.localeCompare(b.category); - }); - }, [playerStructuresQuery]); - - const getPlayerRealms = (filterFn?: (realm: RealmWithPosition) => boolean) => { - return useMemo(() => { - const realms = filterFn ? playerRealms.filter(filterFn) : playerRealms; - return realms.sort((a, b) => a.name.localeCompare(b.name)); - }, [playerRealms, filterFn]); - }; - - const getPlayerStructures = (filterFn?: (structure: PlayerStructure) => boolean) => { - return useMemo(() => { - const structures = filterFn ? playerStructures.filter(filterFn) : playerStructures; - return structures.sort((a, b) => a.name.localeCompare(b.name)); - }, [playerStructures, filterFn]); - }; - - return { - playerRealms: getPlayerRealms, - playerStructures: getPlayerStructures, - }; + return playerRealms; }; -export const useEntitiesUtils = () => { +export const usePlayerStructures = (playerAddress?: ContractAddress) => { const { account: { account }, - setup: { - components: { - Army, - EntityName, - ArrivalTime, - EntityOwner, - Movable, - CapacityCategory, - CapacityConfig, - Position, - AddressName, - Owner, - Realm, - Structure, - }, - }, + setup: { components }, } = useDojo(); - const { getResourcesFromBalance } = useResourcesUtils(); - - const getEntityInfo = (entityId: ID) => { - const entityIdBigInt = BigInt(entityId); - const arrivalTime = getComponentValue(ArrivalTime, getEntityIdFromKeys([entityIdBigInt])); - const movable = getComponentValue(Movable, getEntityIdFromKeys([entityIdBigInt])); - - const entityCapacityCategory = getComponentValue(CapacityCategory, getEntityIdFromKeys([entityIdBigInt])) - ?.category as unknown as string; - const capacityCategoryId = CAPACITY_CONFIG_CATEGORY_STRING_MAP[entityCapacityCategory] || 0n; - const capacity = getComponentValue(CapacityConfig, getEntityIdFromKeys([BigInt(capacityCategoryId)])); - - const entityOwner = getComponentValue(EntityOwner, getEntityIdFromKeys([entityIdBigInt])); - const owner = getComponentValue(Owner, getEntityIdFromKeys([BigInt(entityOwner?.entity_owner_id || 0)])); - - const name = getEntityName(entityId); - - const structure = getComponentValue(Structure, getEntityIdFromKeys([entityIdBigInt])); - - const resources = getResourcesFromBalance(entityId); - const army = getComponentValue(Army, getEntityIdFromKeys([entityIdBigInt])); - const rawIntermediateDestination = movable - ? { x: movable.intermediate_coord_x, y: movable.intermediate_coord_y } - : undefined; - const intermediateDestination = rawIntermediateDestination - ? { x: rawIntermediateDestination.x, y: rawIntermediateDestination.y } - : undefined; - - const position = getComponentValue(Position, getEntityIdFromKeys([entityIdBigInt])); - - const homePosition = entityOwner - ? getComponentValue(Position, getEntityIdFromKeys([BigInt(entityOwner?.entity_owner_id || 0)])) - : undefined; - - return { - entityId, - arrivalTime: arrivalTime?.arrives_at, - blocked: Boolean(movable?.blocked), - capacity: divideByPrecision(Number(capacity?.weight_gram) || 0), - intermediateDestination, - position: position ? { x: position.x, y: position.y } : undefined, - homePosition: homePosition ? { x: homePosition.x, y: homePosition.y } : undefined, - owner: owner?.address, - isMine: ContractAddress(owner?.address || 0n) === ContractAddress(account.address), - isRoundTrip: movable?.round_trip || false, - resources, - entityType: army ? EntityType.TROOP : EntityType.DONKEY, - structureCategory: structure?.category, - structure, - name, - }; - }; - - const getEntityName = (entityId: ID, abbreviate: boolean = false) => { - const entityName = getComponentValue(EntityName, getEntityIdFromKeys([BigInt(entityId)])); - const realm = getComponentValue(Realm, getEntityIdFromKeys([BigInt(entityId)])); - const structure = getComponentValue(Structure, getEntityIdFromKeys([BigInt(entityId)])); - if (structure?.category === StructureType[StructureType.Realm] && realm) { - return getRealmName(realm); - } - - if (entityName) { - return shortString.decodeShortString(entityName.name.toString()); - } - - if (abbreviate && structure) { - const abbreviations: Record = { - [StructureType[StructureType.FragmentMine]]: "FM", - [StructureType[StructureType.Hyperstructure]]: "HS", - [StructureType[StructureType.Bank]]: "BK", - }; - - const abbr = abbreviations[structure.category]; - if (abbr) { - return `${abbr} ${structure.entity_id}`; - } - } - return `${structure?.category} ${structure?.entity_id}`; - }; - - const getAddressName = (address: ContractAddress) => { - const addressName = getComponentValue(AddressName, getEntityIdFromKeys([BigInt(address)])); - - return addressName ? shortString.decodeShortString(addressName.name.toString()) : undefined; - }; - - const getAddressNameFromEntity = (entityId: ID) => { - const address = getPlayerAddressFromEntity(entityId); - if (!address) return; - - const addressName = getComponentValue(AddressName, getEntityIdFromKeys([BigInt(address)])); - - return addressName ? shortString.decodeShortString(addressName.name.toString()) : undefined; - }; + const entities = useEntityQuery([ + Has(components.Structure), + Has(components.Position), + Has(components.Owner), + HasValue(components.Owner, { address: playerAddress || ContractAddress(account.address) }), + ]); - const getPlayerAddressFromEntity = (entityId: ID): ContractAddress | undefined => { - const entityOwner = getComponentValue(EntityOwner, getEntityIdFromKeys([BigInt(entityId)])); - return entityOwner?.entity_owner_id - ? getComponentValue(Owner, getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)]))?.address - : undefined; - }; + const playerStructures = useMemo(() => { + return entities + .map((id) => getStructure(id, components)) + .filter((structure): structure is PlayerStructure => structure !== undefined) + .sort((a, b) => a.category.localeCompare(b.category)); + }, [entities]); - return { getEntityName, getEntityInfo, getAddressName, getAddressNameFromEntity, getPlayerAddressFromEntity }; + return playerStructures; }; diff --git a/client/apps/game/src/hooks/helpers/use-fragment-mines.tsx b/client/apps/game/src/hooks/helpers/use-fragment-mines.tsx index 12f24ff26..e606bd84e 100644 --- a/client/apps/game/src/hooks/helpers/use-fragment-mines.tsx +++ b/client/apps/game/src/hooks/helpers/use-fragment-mines.tsx @@ -48,5 +48,6 @@ export const useFragmentMines = () => { }; }, ); - return { fragmentMines }; + + return fragmentMines; }; diff --git a/client/apps/game/src/hooks/helpers/use-get-all-players.tsx b/client/apps/game/src/hooks/helpers/use-get-all-players.tsx deleted file mode 100644 index 701260b6f..000000000 --- a/client/apps/game/src/hooks/helpers/use-get-all-players.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; -import { usePrizePool } from "@/hooks/helpers/use-rewards"; -import { useLeaderBoardStore } from "@/hooks/store/use-leaderboard-store"; -import { calculateLordsShare, calculatePlayerSharePercentage } from "@/ui/utils/leaderboard"; -import { Player, StructureType } from "@bibliothecadao/eternum"; -import { getComponentValue, Has, HasValue, runQuery } from "@dojoengine/recs"; -import { shortString } from "starknet"; -import { formatEther } from "viem"; - -export const useGetAllPlayers = () => { - const dojo = useDojo(); - - const { - setup: { - components: { Realm, Owner, GuildMember, AddressName, Hyperstructure, Structure }, - }, - } = dojo; - const { getEntityName } = useEntitiesUtils(); - const playersByRank = useLeaderBoardStore((state) => state.playersByRank); - - const prizePool = usePrizePool(); - - const playerEntities = runQuery([Has(AddressName)]); - - const totalPoints = playersByRank.reduce((sum, [, points]) => sum + points, 0); - - const getPlayers = (): Player[] => { - const players = Array.from(Array.from(playerEntities)) - .map((id) => { - const addressName = getComponentValue(AddressName, id); - if (!addressName) return; - - const isAlive = !!runQuery([HasValue(Owner, { address: addressName.address })]).size; - - const guildMember = getComponentValue(GuildMember, id); - const guildName = guildMember ? getEntityName(guildMember.guild_entity_id) : ""; - - return { - address: addressName.address, - addressName: shortString.decodeShortString(addressName.name.toString()), - isAlive, - guildName, - }; - }) - .filter((player) => player !== undefined); - - let unrankedCount = 0; - - return players.map((player) => { - const rankIndex = playersByRank.findIndex(([address]) => address === player.address); - if (rankIndex === -1) unrankedCount++; - - const points = rankIndex === -1 ? 0 : playersByRank[rankIndex][1]; - - return { - name: player.addressName, - address: player.address, - points, - rank: rankIndex === -1 ? Number.MAX_SAFE_INTEGER : rankIndex + 1, - percentage: calculatePlayerSharePercentage(points, totalPoints), - lords: calculateLordsShare(points, totalPoints, Number(formatEther(prizePool))), - realms: runQuery([Has(Realm), HasValue(Owner, { address: player.address })]).size, - mines: runQuery([ - HasValue(Structure, { category: StructureType[StructureType.FragmentMine] }), - HasValue(Owner, { address: player.address }), - ]).size, - hyperstructures: runQuery([Has(Hyperstructure), HasValue(Owner, { address: player.address })]).size, - isAlive: player.isAlive, - guildName: player.guildName || "", - }; - }); - }; - - return getPlayers; -}; diff --git a/client/apps/game/src/hooks/helpers/use-guilds.tsx b/client/apps/game/src/hooks/helpers/use-guilds.tsx index ecc02c043..5d363f03e 100644 --- a/client/apps/game/src/hooks/helpers/use-guilds.tsx +++ b/client/apps/game/src/hooks/helpers/use-guilds.tsx @@ -1,9 +1,8 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; -import { useRealm } from "@/hooks/helpers/use-realm"; import { useLeaderBoardStore } from "@/hooks/store/use-leaderboard-store"; import useUIStore from "@/hooks/store/use-ui-store"; import { formatTime, toHexString } from "@/ui/utils/utils"; +import { getAddressName, getEntityName } from "@/utils/entities"; import { ClientComponents, ContractAddress, @@ -11,7 +10,7 @@ import { GuildMemberInfo, GuildWhitelistInfo, ID, - Player, + PlayerInfo, } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; import { Component, Entity, Has, HasValue, NotValue, getComponentValue, runQuery } from "@dojoengine/recs"; @@ -73,7 +72,7 @@ const formatGuilds = ( const formatGuildMembers = ( guildMembers: Entity[], - players: Player[], + players: PlayerInfo[], nextBlockTimestamp: number | undefined, userAddress: string, getAddressName: (address: ContractAddress) => string | undefined, @@ -118,7 +117,7 @@ const formatGuildMembers = ( const formatGuildWhitelist = ( whitelist: Entity[], - players: Player[], + players: PlayerInfo[], GuildWhitelist: Component, getAddressName: (address: ContractAddress) => string | undefined, getEntityName: (entityId: ID) => string, @@ -170,19 +169,18 @@ export const useGuilds = () => { const { setup: { - components: { - Guild, - GuildMember, - GuildWhitelist, - Owner, - AddressName, - events: { CreateGuild, JoinGuild }, - }, + components, account: { account }, }, } = dojo; - const { getEntityName } = useEntitiesUtils(); - const { getAddressName } = useRealm(); + const { + Guild, + GuildMember, + GuildWhitelist, + Owner, + AddressName, + events: { CreateGuild, JoinGuild }, + } = components; const nextBlockTimestamp = useUIStore.getState().nextBlockTimestamp; @@ -196,7 +194,7 @@ export const useGuilds = () => { guilds, nextBlockTimestamp, account.address, - (entityId: number) => getEntityName(entityId) || "Unknown", + (entityId: number) => getEntityName(entityId, components) || "Unknown", Guild, Owner, GuildMember, @@ -205,25 +203,28 @@ export const useGuilds = () => { }; }; - const getGuildFromEntityId = useCallback((entityId: ID, accountAddress: ContractAddress) => { - const guildsRanked = useLeaderBoardStore.getState().guildsByRank; - const guild = formatGuilds( - guildsRanked, - [getEntityIdFromKeys([BigInt(entityId)])], - nextBlockTimestamp, - account.address, - (entityId: number) => getEntityName(entityId) || "Unknown", - Guild, - Owner, - GuildMember, - CreateGuild, - )[0]; - if (!guild) return; - - const owner = getComponentValue(Owner, getEntityIdFromKeys([BigInt(guild.entityId)])); - - return { guild, isOwner: owner?.address === ContractAddress(accountAddress), name: guild.name }; - }, []); + const getGuildFromEntityId = useCallback( + (entityId: ID, accountAddress: ContractAddress, components: ClientComponents) => { + const guildsRanked = useLeaderBoardStore.getState().guildsByRank; + const guild = formatGuilds( + guildsRanked, + [getEntityIdFromKeys([BigInt(entityId)])], + nextBlockTimestamp, + account.address, + (entityId: number) => getEntityName(entityId, components) || "Unknown", + Guild, + Owner, + GuildMember, + CreateGuild, + )[0]; + if (!guild) return; + + const owner = getComponentValue(Owner, getEntityIdFromKeys([BigInt(guild.entityId)])); + + return { guild, isOwner: owner?.address === ContractAddress(accountAddress), name: guild.name }; + }, + [], + ); const getGuildFromPlayerAddress = useCallback((accountAddress: ContractAddress): GuildInfo | undefined => { const guildMember = getComponentValue(GuildMember, getEntityIdFromKeys([accountAddress])); @@ -232,7 +233,9 @@ export const useGuilds = () => { const guild = getComponentValue(Guild, getEntityIdFromKeys([BigInt(guildMember.guild_entity_id)])); const owner = getComponentValue(Owner, getEntityIdFromKeys([BigInt(guildMember.guild_entity_id)])); - const name = guildMember.guild_entity_id ? getEntityName(guildMember.guild_entity_id) || "Unknown" : "Unknown"; + const name = guildMember.guild_entity_id + ? getEntityName(guildMember.guild_entity_id, components) || "Unknown" + : "Unknown"; return { entityId: guildMember?.guild_entity_id, @@ -242,7 +245,7 @@ export const useGuilds = () => { }; }, []); - const useGuildMembers = (guildEntityId: ID, players: Player[]) => { + const useGuildMembers = (guildEntityId: ID, players: PlayerInfo[]) => { const guildMembers = useEntityQuery([HasValue(GuildMember, { guild_entity_id: guildEntityId })]); return { @@ -251,7 +254,7 @@ export const useGuilds = () => { players, nextBlockTimestamp, account.address, - getAddressName, + (address: ContractAddress) => getAddressName(address, components), GuildMember, Owner, JoinGuild, @@ -259,7 +262,7 @@ export const useGuilds = () => { }; }; - const useGuildWhitelist = (guildEntityId: ID, players: Player[]) => { + const useGuildWhitelist = (guildEntityId: ID, players: PlayerInfo[]) => { const whitelist = useEntityQuery([ HasValue(GuildWhitelist, { guild_entity_id: guildEntityId, is_whitelisted: true }), ]); @@ -268,15 +271,19 @@ export const useGuilds = () => { whitelist, players, GuildWhitelist, - getAddressName, - (entityId: number) => getEntityName(entityId) || "Unknown", + (address: ContractAddress) => getAddressName(address, components) || "Unknown", + (entityId: number) => getEntityName(entityId, components) || "Unknown", ); }; const usePlayerWhitelist = (address: ContractAddress) => { const whitelist = useEntityQuery([HasValue(GuildWhitelist, { address, is_whitelisted: true })]); - return formatPlayerWhitelist(whitelist, GuildWhitelist, (entityId: number) => getEntityName(entityId) || "Unknown"); + return formatPlayerWhitelist( + whitelist, + GuildWhitelist, + (entityId: number) => getEntityName(entityId, components) || "Unknown", + ); }; const getPlayersInPlayersGuild = useCallback((accountAddress: ContractAddress) => { diff --git a/client/apps/game/src/hooks/helpers/use-hyperstructures.tsx b/client/apps/game/src/hooks/helpers/use-hyperstructures.tsx index 82b393767..a27113bf7 100644 --- a/client/apps/game/src/hooks/helpers/use-hyperstructures.tsx +++ b/client/apps/game/src/hooks/helpers/use-hyperstructures.tsx @@ -1,7 +1,7 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import { divideByPrecision, toHexString, toInteger } from "@/ui/utils/utils"; +import { getAddressNameFromEntity } from "@/utils/entities"; import { ClientComponents, ContractAddress, @@ -25,12 +25,10 @@ export type ProgressWithPercentage = { export const useHyperstructures = () => { const { account: { account }, - setup: { - components: { Structure, Contribution, Position, Owner, EntityName, Hyperstructure }, - }, + setup: { components }, } = useDojo(); - const { getAddressNameFromEntity } = useEntitiesUtils(); + const { Structure, Contribution, Position, Owner, EntityName, Hyperstructure } = components; const hyperstructures = useEntityQuery([Has(Structure), HasValue(Structure, { category: "Hyperstructure" })]).map( (hyperstructureEntityId) => { @@ -48,7 +46,7 @@ export const useHyperstructures = () => { const owner = toHexString(ownerComponent?.address || 0n); const isOwner = ContractAddress(ownerComponent?.address ?? 0n) === ContractAddress(account.address); const entityName = getComponentValue(EntityName, hyperstructureEntityId); - const ownerName = hyperstructure ? getAddressNameFromEntity(hyperstructure.entity_id!) : ""; + const ownerName = hyperstructure ? getAddressNameFromEntity(hyperstructure.entity_id!, components) : ""; return { ...hyperstructure, diff --git a/client/apps/game/src/hooks/helpers/use-players.tsx b/client/apps/game/src/hooks/helpers/use-players.tsx new file mode 100644 index 000000000..5defa5771 --- /dev/null +++ b/client/apps/game/src/hooks/helpers/use-players.tsx @@ -0,0 +1,25 @@ +import { useDojo } from "@/hooks/context/dojo-context"; +import { Player } from "@bibliothecadao/eternum"; +import { useEntityQuery } from "@dojoengine/react"; +import { getComponentValue, Has } from "@dojoengine/recs"; +import { useMemo } from "react"; + +export const usePlayers = (): Player[] => { + const { + setup: { components }, + } = useDojo(); + + const entities = useEntityQuery([Has(components.AddressName)]); + + const players = useMemo(() => { + return entities + .map((id) => { + const addressName = getComponentValue(components.AddressName, id); + if (!addressName) return; + return { entity: id, address: addressName.address, name: addressName.name.toString() }; + }) + .filter(Boolean) as Player[]; + }, [entities]); + + return players; +}; diff --git a/client/apps/game/src/hooks/helpers/use-quests.tsx b/client/apps/game/src/hooks/helpers/use-quests.tsx index e8bea46a7..4f4a7c6b8 100644 --- a/client/apps/game/src/hooks/helpers/use-quests.tsx +++ b/client/apps/game/src/hooks/helpers/use-quests.tsx @@ -1,53 +1,40 @@ import { useDojo } from "@/hooks/context/dojo-context"; import { useArmiesByStructure } from "@/hooks/helpers/use-armies"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import { useGetMyOffers } from "@/hooks/helpers/use-trade"; import useUIStore from "@/hooks/store/use-ui-store"; import { questDetails } from "@/ui/components/quest/quest-details"; -import { ArmyInfo, BuildingType, ContractAddress, ID, QuestType, TileManager } from "@bibliothecadao/eternum"; +import { armyHasTraveled } from "@/utils/army"; +import { getEntityInfo } from "@/utils/entities"; +import { ContractAddress, Prize, QuestStatus, QuestType, TileManager } from "@bibliothecadao/eternum"; import { useComponentValue, useEntityQuery } from "@dojoengine/react"; -import { HasValue, getComponentValue } from "@dojoengine/recs"; +import { getComponentValue, HasValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { useMemo } from "react"; - -export interface Prize { - id: QuestType; - title: string; -} - -export enum QuestStatus { - InProgress, - Completed, - Claimed, -} +import { useBuildingQuantities } from "./use-buildings"; export const useQuests = () => { const questDependencies = useQuestDependencies(); - const createQuest = (QuestType: QuestType) => { - const dependency = questDependencies[QuestType]; - return useMemo( - () => ({ - id: QuestType, - ...questDetails.get(QuestType)!, - status: dependency.status, - }), - [questDependencies[QuestType]], - ); - }; - - const quests = [ - createQuest(QuestType.Settle), - createQuest(QuestType.BuildFood), - createQuest(QuestType.BuildResource), - createQuest(QuestType.PauseProduction), - createQuest(QuestType.CreateDefenseArmy), - createQuest(QuestType.CreateAttackArmy), - createQuest(QuestType.Travel), - createQuest(QuestType.CreateTrade), + const questTypes = [ + QuestType.Settle, + QuestType.BuildFood, + QuestType.BuildResource, + QuestType.PauseProduction, + QuestType.CreateDefenseArmy, + QuestType.CreateAttackArmy, + QuestType.Travel, + QuestType.CreateTrade, ]; - return { quests }; + const quests = useMemo(() => { + return questTypes.map((type) => ({ + id: type, + ...questDetails.get(type)!, + status: questDependencies[type].status, + })); + }, [questDependencies]); + + return quests; }; const useQuestDependencies = () => { @@ -63,10 +50,13 @@ const useQuestDependencies = () => { structureEntityId: structureEntityId || 0, }); const orders = useGetMyOffers(); - const { getEntityInfo } = useEntitiesUtils(); const structurePosition = useMemo( - () => getEntityInfo(structureEntityId)?.position || { x: 0, y: 0 }, + () => + getEntityInfo(structureEntityId, ContractAddress(setup.account.account.address), setup.components)?.position || { + x: 0, + y: 0, + }, [structureEntityId, getEntityInfo], ); @@ -96,7 +86,7 @@ const useQuestDependencies = () => { ); const { questClaimStatus } = useQuestClaimStatus(); - const { unclaimedQuestsCount } = useUnclaimedQuestsCount(); + const unclaimedQuestsCount = useUnclaimedQuestsCount(); return useMemo( () => ({ @@ -215,43 +205,5 @@ export const useUnclaimedQuestsCount = () => { [questClaimStatus], ); - return { unclaimedQuestsCount }; -}; - -const useBuildingQuantities = (structureEntityId: ID | undefined) => { - const { - setup: { - components: { BuildingQuantityv2 }, - }, - } = useDojo(); - const entityUpdate = useEntityQuery([HasValue(BuildingQuantityv2, { entity_id: structureEntityId || 0 })]); - const getBuildingQuantity = (buildingType: BuildingType) => - getComponentValue(BuildingQuantityv2, getEntityIdFromKeys([BigInt(structureEntityId || 0), BigInt(buildingType)])) - ?.value || 0; - - return useMemo( - () => ({ - food: getBuildingQuantity(BuildingType.Farm) + getBuildingQuantity(BuildingType.FishingVillage), - resource: getBuildingQuantity(BuildingType.Resource), - workersHut: getBuildingQuantity(BuildingType.WorkersHut), - markets: getBuildingQuantity(BuildingType.Market), - }), - [structureEntityId, entityUpdate], - ); -}; - -export const armyHasTroops = (entityArmies: (ArmyInfo | undefined)[]) => { - return entityArmies.some( - (army) => - army && - (Number(army.troops.knight_count) !== 0 || - Number(army.troops.crossbowman_count) !== 0 || - Number(army.troops.paladin_count) !== 0), - ); -}; - -const armyHasTraveled = (entityArmies: ArmyInfo[], realmPosition: { x: number; y: number }) => { - return entityArmies.some( - (army) => army && realmPosition && (army.position.x !== realmPosition.x || army.position.y !== realmPosition.y), - ); + return unclaimedQuestsCount; }; diff --git a/client/apps/game/src/hooks/helpers/use-realm.tsx b/client/apps/game/src/hooks/helpers/use-realm.tsx index 5b4e42c9f..c4ae8ea2d 100644 --- a/client/apps/game/src/hooks/helpers/use-realm.tsx +++ b/client/apps/game/src/hooks/helpers/use-realm.tsx @@ -1,344 +1,47 @@ -import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import useUIStore from "@/hooks/store/use-ui-store"; -import { unpackResources } from "@/ui/utils/packed-data"; -import { getRealmNameById } from "@/ui/utils/realms"; -import { getEntityIdFromKeys } from "@/ui/utils/utils"; -import { - ContractAddress, - getOrderName, - getQuestResources as getStartingResources, - type ClientComponents, - type ID, -} from "@bibliothecadao/eternum"; +import { getRealmInfo } from "@/utils/realm"; +import { ClientComponents, ContractAddress, RealmInfo } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; -import { Has, HasValue, getComponentValue, runQuery, type ComponentValue, type Entity } from "@dojoengine/recs"; +import { ComponentValue, getComponentValue, Has, HasValue } from "@dojoengine/recs"; import { useMemo } from "react"; -import { shortString } from "starknet"; -import realmIdsByOrder from "../../../../../common/data/realmids_by_order.json"; -export interface RealmInfo { - realmId: ID; - entityId: ID; - name: string; - resourceTypesPacked: bigint; - order: number; - position: ComponentValue; - population?: number | undefined; - capacity?: number; - hasCapacity: boolean; - owner: ContractAddress; - ownerName: string; - hasWonder: boolean; -} - -export function useRealm() { - const { - setup: { - components: { Realm, AddressName, Owner, EntityOwner, Position, Structure }, - }, - } = useDojo(); - const structureEntityId = useUIStore((state) => state.structureEntityId); - - const getQuestResources = () => { - const realm = getComponentValue(Realm, getEntityIdFromKeys([BigInt(structureEntityId)])); - const resourcesProduced = realm ? unpackResources(realm.produced_resources) : []; - return getStartingResources(resourcesProduced); - }; - - const getEntityOwner = (entityId: ID) => { - const entityOwner = getComponentValue(EntityOwner, getEntityIdFromKeys([BigInt(entityId)])); - return entityOwner?.entity_owner_id; - }; - - const isRealmIdSettled = (realmId: ID) => { - const entityIds = runQuery([HasValue(Realm, { realm_id: realmId })]); - return entityIds.size > 0; - }; - - const getRandomUnsettledRealmId = () => { - // Query all settled realms and collect their realm_ids - const entityIds = Array.from(runQuery([Has(Realm)])); - const settledRealmIds = new Set(); - - entityIds.forEach((entityId) => { - const realm = getComponentValue(Realm, getEntityIdFromKeys([BigInt(entityId)])); - if (realm) { - settledRealmIds.add(Number(realm.realm_id)); - } - }); - - // Define all possible realm_ids from 1 to 8000 - const TOTAL_REALMS = 8000; - const allRealmIds = Array.from({ length: TOTAL_REALMS }, (_, i) => i + 1); - - // Determine unsettled realm_ids by excluding settled ones - const unsettledRealmIds = allRealmIds.filter((id) => !settledRealmIds.has(id)); - - if (unsettledRealmIds.length === 0) { - throw new Error("No unsettled realms available."); - } - - // Select a random unsettled realm ID - const randomIndex = Math.floor(Math.random() * unsettledRealmIds.length); - return unsettledRealmIds[randomIndex]; - }; - - const getNextRealmIdForOrder = (order: number) => { - const orderName = getOrderName(order); - - const entityIds = Array.from(runQuery([HasValue(Realm, { order })])); - const realmEntityIds = entityIds.map((id) => { - return getComponentValue(Realm, id)!.entity_id; - }); - - let latestRealmIdFromOrder = 0; - if (realmEntityIds.length > 0) { - const realmEntityId = realmEntityIds.sort((a, b) => Number(b) - Number(a))[0]; - const latestRealmFromOrder = getComponentValue(Realm, getEntityIdFromKeys([BigInt(realmEntityId)])); - if (latestRealmFromOrder) { - latestRealmIdFromOrder = Number(latestRealmFromOrder.realm_id); - } - } - - const orderRealmIds = (realmIdsByOrder as Record)[orderName]; - let nextRealmIdFromOrder = 0; - - const maxIterations = orderRealmIds.length; - for (let i = 0; i < maxIterations; i++) { - // sort from biggest to lowest - const latestIndex = orderRealmIds.indexOf(latestRealmIdFromOrder); - - if (latestIndex === -1 || latestIndex === orderRealmIds.length - 1) { - nextRealmIdFromOrder = orderRealmIds[0]; - } else { - nextRealmIdFromOrder = orderRealmIds[latestIndex + 1]; - } - - return nextRealmIdFromOrder; - } - - throw new Error(`Could not find an unoccupied realm ID for order ${orderName} after ${maxIterations} attempts`); - }; - - const getRealmEntityIdFromRealmId = (realmId: ID): ID | undefined => { - const realmEntityIds = runQuery([HasValue(Realm, { realm_id: realmId })]); - if (realmEntityIds.size > 0) { - const realm = getComponentValue(Realm, realmEntityIds.values().next().value || ("" as Entity)); - return realm!.entity_id; - } - }; - - const getRealmIdFromRealmEntityId = (realmEntityId: ID) => { - const realm = getComponentValue(Realm, getEntityIdFromKeys([BigInt(realmEntityId)])); - return realm?.realm_id; - }; - - const getRealmIdForOrderAfter = (order: number, realmId: ID): ID => { - const orderName = getOrderName(order); - - const orderRealmIds = (realmIdsByOrder as Record)[orderName]; - const latestIndex = orderRealmIds.indexOf(realmId); - - if (latestIndex === -1 || latestIndex === orderRealmIds.length - 1) { - return orderRealmIds[0]; - } else { - return orderRealmIds[latestIndex + 1]; - } - }; - - const getAddressName = (address: ContractAddress): string | undefined => { - const addressName = getComponentValue(AddressName, getEntityIdFromKeys([address])); - - return addressName ? shortString.decodeShortString(addressName.name.toString()) : undefined; - }; - - const getAddressOrder = (address: ContractAddress) => { - const ownedRealms = runQuery([Has(Realm), HasValue(Owner, { address })]); - if (ownedRealms.size > 0) { - const realm = getComponentValue(Realm, ownedRealms.values().next().value || ("" as Entity)); - return realm?.order; - } - }; - - const getRealmAddressName = (realmEntityId: ID) => { - const owner = getComponentValue(Owner, getEntityIdFromKeys([BigInt(realmEntityId)])); - const addressName = owner ? getComponentValue(AddressName, getEntityIdFromKeys([owner.address])) : undefined; - - if (addressName) { - return shortString.decodeShortString(String(addressName.name)); - } else { - return ""; - } - }; - - const getRealmEntityIdsOnPosition = (x: number, y: number) => { - const entityIds = runQuery([Has(Realm), HasValue(Position, { x, y })]); - const realmEntityIds = Array.from(entityIds).map((entityId) => { - return getComponentValue(Realm, entityId)!.entity_id; - }); - return realmEntityIds.length === 1 ? realmEntityIds[0] : undefined; - }; - - const isEntityIdRealm = (entityId: ID) => { - const realm = getComponentValue(Realm, getEntityIdFromKeys([BigInt(entityId)])); - return !!realm; - }; - - return { - getQuestResources, - getEntityOwner, - isRealmIdSettled, - getNextRealmIdForOrder, - getAddressName, - getAddressOrder, - getRealmAddressName, - getRealmIdForOrderAfter, - getRealmIdFromRealmEntityId, - getRealmEntityIdFromRealmId, - isEntityIdRealm, - getRealmEntityIdsOnPosition, - getRandomUnsettledRealmId, - }; -} - -export function useGetRealm(realmEntityId: ID | undefined) { - const { - setup: { - components: { Realm, Position, Owner, Population }, - }, - } = useDojo(); - - const query = useEntityQuery([HasValue(Realm, { entity_id: realmEntityId })]); - - const realm = (): any => { - if (realmEntityId !== undefined) { - const entityId = getEntityIdFromKeys([BigInt(realmEntityId)]); - const realm = getComponentValue(Realm, entityId); - const owner = getComponentValue(Owner, entityId); - const position = getComponentValue(Position, entityId); - const population = getComponentValue(Population, entityId); - - if (realm && owner && position) { - const { realm_id, entity_id, produced_resources, order, level } = realm; - - const name = getRealmNameById(realm_id); - - const { address } = owner; - - return { - realmId: realm_id, - entityId: entity_id, - name, - level, - resourceTypesPacked: produced_resources, - order, - position, - ...population, - hasCapacity: - !population || population.capacity + configManager.getBasePopulationCapacity() > population.population, - owner: address, - hasWonder: realm.has_wonder, - }; - } - } - }; - - return { - realm: realm(), - }; -} - -export function getRealms(): RealmInfo[] { +export function usePlayerRealms(): RealmInfo[] { const { - setup: { - components: { Realm, Position, Owner, Population, AddressName, Structure }, - }, + account: { account }, + setup: { components }, } = useDojo(); - const realmEntities = runQuery([Has(Realm)]); - - return Array.from(realmEntities) - .map((entity) => { - const realm = getComponentValue(Realm, entity); - const owner = getComponentValue(Owner, entity); - const position = getComponentValue(Position, entity); - const population = getComponentValue(Population, entity); - - if (!realm || !owner || !position) return null; + const { Realm, Owner } = components; - const { realm_id, entity_id, produced_resources, order } = realm; - - const name = getRealmNameById(realm_id); - - const { address } = owner; + const realmEntities = useEntityQuery([Has(Realm), HasValue(Owner, { address: ContractAddress(account.address) })]); - const addressName = getComponentValue(AddressName, getEntityIdFromKeys([address])); - const ownerName = shortString.decodeShortString(addressName?.name.toString() ?? "0x0"); + const realms = useMemo(() => { + return realmEntities + .map((entity) => { + return getRealmInfo(entity, components); + }) + .filter(Boolean) as RealmInfo[]; + }, [realmEntities]); - return { - realmId: realm_id, - entityId: entity_id, - name, - resourceTypesPacked: produced_resources, - order, - position, - ...population, - hasCapacity: - !population || population.capacity + configManager.getBasePopulationCapacity() > population.population, - owner: address, - ownerName, - hasWonder: realm.has_wonder, - }; - }) - .filter((realm): realm is RealmInfo => realm !== null); + return realms; } -export function usePlayerRealms(): RealmInfo[] { +export const useRealms = () => { const { - account: { account }, setup: { - components: { Realm, Position, Owner, Population, AddressName, Structure }, + components: { Realm }, }, } = useDojo(); - const realmEntities = useEntityQuery([Has(Realm), HasValue(Owner, { address: ContractAddress(account.address) })]); + const realmEntities = useEntityQuery([Has(Realm)]); - const realms = useMemo((): RealmInfo[] => { - return Array.from(realmEntities) + const realms = useMemo(() => { + return realmEntities .map((entity) => { - const realm = getComponentValue(Realm, entity); - const owner = getComponentValue(Owner, entity); - const position = getComponentValue(Position, entity); - const population = getComponentValue(Population, entity); - - if (!realm || !owner || !position) return null; - - const { realm_id, entity_id, produced_resources, order } = realm; - - const name = getRealmNameById(realm_id); - - const { address } = owner; - - const addressName = getComponentValue(AddressName, getEntityIdFromKeys([address])); - const ownerName = shortString.decodeShortString(addressName?.name.toString() ?? "0x0"); - - return { - realmId: realm_id, - entityId: entity_id, - name, - resourceTypesPacked: produced_resources, - order, - position, - ...population, - hasCapacity: - !population || population.capacity + configManager.getBasePopulationCapacity() > population.population, - owner: address, - ownerName, - hasWonder: realm.has_wonder, - }; + return getComponentValue(Realm, entity); }) - .filter((realm): realm is RealmInfo => realm !== null); + .filter(Boolean) as ComponentValue[]; }, [realmEntities]); + return realms; -} +}; diff --git a/client/apps/game/src/hooks/helpers/use-resource-arrivals.tsx b/client/apps/game/src/hooks/helpers/use-resource-arrivals.tsx index dcc886dcc..4e3c96c19 100644 --- a/client/apps/game/src/hooks/helpers/use-resource-arrivals.tsx +++ b/client/apps/game/src/hooks/helpers/use-resource-arrivals.tsx @@ -1,25 +1,17 @@ import { useDojo } from "@/hooks/context/dojo-context"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; -import { ContractAddress, ID, Position } from "@bibliothecadao/eternum"; +import { + ArrivalInfo, + ContractAddress, + DONKEY_RESOURCE_TRACKER, + LORDS_AND_DONKEY_RESOURCE_TRACKER, + LORDS_RESOURCE_TRACKER, +} from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; import { Entity, Has, HasValue, NotValue, defineQuery, getComponentValue, isComponentUpdate } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { useCallback, useEffect, useMemo, useState } from "react"; -const DONKEY_RESOURCE_TRACKER = 452312848583266388373324160190187140051835877600158453279131187530910662656n; -const LORDS_RESOURCE_TRACKER = 7237005577332262213973186563042994240829374041602535252466099000494570602496n; -const LORDS_AND_DONKEY_RESOURCE_TRACKER = 7689318425915528602346510723233181380881209919202693705745230188025481265152n; - -export type ArrivalInfo = { - entityId: ID; - recipientEntityId: ID; - position: Position; - arrivesAt: bigint; - isOwner: boolean; - hasResources: boolean; - isHome: boolean; -}; - const getCurrentDonkeyWeightMinimum = () => { return Number(localStorage.getItem("WEIGHT_MINIMUM") || 0) * 1000; }; diff --git a/client/apps/game/src/hooks/helpers/use-resources.tsx b/client/apps/game/src/hooks/helpers/use-resources.tsx index 1744130b9..6d954f38d 100644 --- a/client/apps/game/src/hooks/helpers/use-resources.tsx +++ b/client/apps/game/src/hooks/helpers/use-resources.tsx @@ -1,176 +1,9 @@ -import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import useUIStore from "@/hooks/store/use-ui-store"; -import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; -import { - CapacityConfigCategory, - ID, - ResourceManager, - ResourcesIds, - resources, - type Resource, -} from "@bibliothecadao/eternum"; +import { ID, ResourceManager, ResourcesIds } from "@bibliothecadao/eternum"; import { useComponentValue } from "@dojoengine/react"; -import { Has, HasValue, getComponentValue, runQuery, type Entity } from "@dojoengine/recs"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo } from "react"; import { getEntityIdFromKeys } from "../../ui/utils/utils"; -export function useResourcesUtils() { - const { setup } = useDojo(); - const { - components: { Weight, Resource, ResourceCost, Realm, CapacityCategory }, - } = setup; - - const weightLessResources = useMemo(() => { - return configManager.getWeightLessResources(); - }, []); - - const useResourcesFromBalance = (entityId: ID) => { - const { currentDefaultTick } = useNextBlockTimestamp(); - const weight = useComponentValue(Weight, getEntityIdFromKeys([BigInt(entityId)])); - const capacityCategory = useComponentValue(CapacityCategory, getEntityIdFromKeys([BigInt(entityId)])); - - return useMemo(() => { - if (!weight?.value && capacityCategory?.category !== CapacityConfigCategory[CapacityConfigCategory.Structure]) - return []; - - return resources - .map(({ id }) => { - const resourceManager = new ResourceManager(setup.components, entityId, id); - const balance = resourceManager.balance(currentDefaultTick); - return { resourceId: id, amount: balance }; - }) - .filter(({ amount }) => amount > 0); - }, [weight, entityId, currentDefaultTick]); - }; - - const getResourcesFromBalance = (entityId: ID): Resource[] => { - const currentDefaultTick = useUIStore.getState().currentDefaultTick; - - const weight = getComponentValue(Weight, getEntityIdFromKeys([BigInt(entityId)])); - const hasWeightlessResources = weightLessResources.some( - (resourceId) => - (getComponentValue(Resource, getEntityIdFromKeys([BigInt(entityId), BigInt(resourceId)]))?.balance ?? 0n) > 0n, - ); - if (!weight?.value && !hasWeightlessResources) return []; - const resourceIds = resources.map((r) => r.id); - return resourceIds - .map((id) => { - const resourceManager = new ResourceManager(setup.components, entityId, id); - const balance = resourceManager.balance(currentDefaultTick); - return { resourceId: id, amount: balance }; - }) - .filter((r) => r.amount > 0); - }; - - const getResourceCosts = (costUuid: bigint, count: number) => { - const resourceCosts = []; - for (let i = 0; i < count; i++) { - const resourceCost = getComponentValue(ResourceCost, getEntityIdFromKeys([costUuid, BigInt(i)])); - if (resourceCost) { - resourceCosts.push({ resourceId: resourceCost.resource_type, amount: Number(resourceCost.amount) }); - } - } - return resourceCosts; - }; - - const getRealmsWithSpecificResource = ( - resourceId: ResourcesIds, - minAmount: number, - ): Array<{ realmEntityId: ID; realmId: ID; amount: number }> => { - const allRealms = Array.from(runQuery([Has(Realm)])); - const currentDefaultTick = useUIStore.getState().currentDefaultTick; - const realmsWithResource = allRealms - .map((id: Entity) => { - const realm = getComponentValue(Realm, id); - const resourceManager = realm ? new ResourceManager(setup.components, realm.entity_id, resourceId) : undefined; - const resource = resourceManager - ? { - balance: resourceManager.balance(currentDefaultTick), - } - : undefined; - - if (resource && resource.balance > minAmount) { - return { - realmEntityId: realm?.entity_id, - realmId: realm?.realm_id, - amount: Number(resource.balance), - }; - } - }) - .filter(Boolean) as Array<{ realmEntityId: ID; realmId: ID; amount: number }>; - - return realmsWithResource; - }; - - return { - getRealmsWithSpecificResource, - getResourcesFromBalance, - getResourceCosts, - useResourcesFromBalance, - }; -} - -export function useResourceBalance() { - const dojo = useDojo(); - - const getFoodResources = (entityId: ID): Resource[] => { - const currentDefaultTick = useUIStore.getState().currentDefaultTick; - const wheatBalance = new ResourceManager(dojo.setup.components, entityId, ResourcesIds.Wheat).balance( - currentDefaultTick, - ); - const fishBalance = new ResourceManager(dojo.setup.components, entityId, ResourcesIds.Fish).balance( - currentDefaultTick, - ); - - return [ - { resourceId: ResourcesIds.Wheat, amount: wheatBalance }, - { resourceId: ResourcesIds.Fish, amount: fishBalance }, - ]; - }; - - const getResourceProductionInfo = (entityId: ID, resourceId: ResourcesIds) => { - const resourceManager = new ResourceManager(dojo.setup.components, entityId, resourceId); - return resourceManager.getProduction(); - }; - - const getBalance = (entityId: ID, resourceId: ResourcesIds) => { - const currentDefaultTick = useUIStore.getState().currentDefaultTick; - const resourceManager = new ResourceManager(dojo.setup.components, entityId, resourceId); - return { balance: resourceManager.balance(currentDefaultTick), resourceId }; - }; - - const getResourcesBalance = (entityId: ID) => { - const detachedResourceEntityIds = runQuery([ - HasValue(dojo.setup.components.DetachedResource, { entity_id: entityId }), - ]); - return Array.from(detachedResourceEntityIds).map((entityId) => - getComponentValue(dojo.setup.components.DetachedResource, entityId), - ); - }; - - // We should deprecate this hook and use getBalance instead - too many useEffects - const useBalance = (entityId: ID, resourceId: ResourcesIds) => { - const currentDefaultTick = useUIStore.getState().currentDefaultTick; - const [resourceBalance, setResourceBalance] = useState({ amount: 0, resourceId }); - - useEffect(() => { - const resourceManager = new ResourceManager(dojo.setup.components, entityId, resourceId); - setResourceBalance({ amount: resourceManager.balance(currentDefaultTick), resourceId }); - }, []); - - return resourceBalance; - }; - - return { - getFoodResources, - getBalance, - useBalance, - getResourcesBalance, - getResourceProductionInfo, - }; -} - export const useResourceManager = (entityId: ID, resourceId: ResourcesIds) => { const dojo = useDojo(); const production = useComponentValue( diff --git a/client/apps/game/src/hooks/helpers/use-structure-entity-id.tsx b/client/apps/game/src/hooks/helpers/use-structure-entity-id.tsx index 05b2a0cb8..991763644 100644 --- a/client/apps/game/src/hooks/helpers/use-structure-entity-id.tsx +++ b/client/apps/game/src/hooks/helpers/use-structure-entity-id.tsx @@ -1,5 +1,5 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntities } from "@/hooks/helpers/use-entities"; +import { usePlayerStructures } from "@/hooks/helpers/use-entities"; import { useQuery } from "@/hooks/helpers/use-query"; import useUIStore from "@/hooks/store/use-ui-store"; import { Position as PositionInterface } from "@/types/position"; @@ -23,9 +23,7 @@ export const useStructureEntityId = () => { const address = isSpectatorMode ? ContractAddress("0x0") : ContractAddress(account.address); - const { playerStructures } = useEntities(); - - const structures = playerStructures(); + const structures = usePlayerStructures(ContractAddress(account.address)); const defaultPlayerStructure = useMemo(() => { return structures[0]; diff --git a/client/apps/game/src/hooks/helpers/use-structures.tsx b/client/apps/game/src/hooks/helpers/use-structures.tsx index a771ec394..65789047c 100644 --- a/client/apps/game/src/hooks/helpers/use-structures.tsx +++ b/client/apps/game/src/hooks/helpers/use-structures.tsx @@ -1,9 +1,9 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; import { useGetArmyByEntityId } from "@/hooks/helpers/use-armies"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; import { currentTickCount } from "@/ui/utils/utils"; +import { getEntityName } from "@/utils/entities"; import { BattleManager, ContractAddress, @@ -21,14 +21,12 @@ import { shortString } from "starknet"; export const useStructureAtPosition = ({ x, y }: Position): Structure | undefined => { const { account: { account }, - setup: { - components: { Position, Structure, EntityOwner, Owner, Protector, AddressName }, - }, + setup: { components }, } = useDojo(); - const { getArmy } = useGetArmyByEntityId(); + const { Position, Structure, EntityOwner, Owner, Protector, AddressName } = components; - const { getEntityName } = useEntitiesUtils(); + const { getArmy } = useGetArmyByEntityId(); const structure = useMemo(() => { const structureAtPosition = runQuery([HasValue(Position, { x, y }), Has(Structure)]); @@ -45,7 +43,7 @@ export const useStructureAtPosition = ({ x, y }: Position): Structure | undefine const protectorArmy = getComponentValue(Protector, structureEntityId); const protector = protectorArmy ? getArmy(protectorArmy.army_id) : undefined; - const name = getEntityName(structure.entity_id) || ""; + const name = getEntityName(structure.entity_id, components) || ""; const addressName = getComponentValue(AddressName, getEntityIdFromKeys([owner?.address])); const ownerName = addressName ? shortString.decodeShortString(addressName!.name.toString()) : "Bandits"; @@ -68,15 +66,12 @@ export const useStructureAtPosition = ({ x, y }: Position): Structure | undefine export const useStructureByPosition = () => { const { account: { account }, - setup: { - components: { Position, Structure, EntityOwner, Owner, Protector }, - }, + setup: { components }, } = useDojo(); + const { Position, Structure, EntityOwner, Owner, Protector } = components; const { getArmy } = useGetArmyByEntityId(); - const { getEntityName } = useEntitiesUtils(); - const structureAtPosition = ({ x, y }: Position) => { const structureAtPosition = runQuery([HasValue(Position, { x, y }), Has(Structure)]); const structureEntityId = Array.from(structureAtPosition)[0]; @@ -92,7 +87,7 @@ export const useStructureByPosition = () => { const protectorArmy = getComponentValue(Protector, structureEntityId); const protector = protectorArmy ? getArmy(protectorArmy.army_id) : undefined; - const name = getEntityName(structure.entity_id); + const name = getEntityName(structure.entity_id, components); return { ...structure, @@ -111,12 +106,10 @@ export const useStructureByPosition = () => { export const useStructureByEntityId = (entityId: ID) => { const { account: { account }, - setup: { - components: { Structure, EntityOwner, Owner, Protector, Position, AddressName }, - }, + setup: { components }, } = useDojo(); - const { getEntityName } = useEntitiesUtils(); + const { Structure, EntityOwner, Owner, Protector, Position, AddressName } = components; const { getArmy } = useGetArmyByEntityId(); @@ -137,7 +130,7 @@ export const useStructureByEntityId = (entityId: ID) => { const addressName = getComponentValue(AddressName, getEntityIdFromKeys([owner?.address])); const ownerName = addressName ? shortString.decodeShortString(addressName!.name.toString()) : "Bandits"; - const name = getEntityName(entityId); + const name = getEntityName(entityId, components); const position = getComponentValue(Position, structureEntityId); @@ -160,13 +153,12 @@ export const useStructureByEntityId = (entityId: ID) => { export const useStructures = () => { const { account: { account }, - setup: { - components: { Structure, EntityOwner, Owner, Protector, Position, AddressName }, - }, + setup: { components }, } = useDojo(); + const { Structure, EntityOwner, Owner, Protector, Position, AddressName } = components; + const { getArmy } = useGetArmyByEntityId(); - const { getEntityName } = useEntitiesUtils(); const getStructureByEntityId = (entityId: ID) => { const structureEntityId = getEntityIdFromKeys([BigInt(entityId)]); @@ -185,7 +177,7 @@ export const useStructures = () => { const addressName = getComponentValue(AddressName, getEntityIdFromKeys([owner?.address])); const ownerName = addressName ? shortString.decodeShortString(addressName!.name.toString()) : "Bandits"; - const name = getEntityName(entityId); + const name = getEntityName(entityId, components); const position = getComponentValue(Position, structureEntityId); diff --git a/client/apps/game/src/hooks/helpers/use-trade.tsx b/client/apps/game/src/hooks/helpers/use-trade.tsx index 4aadf3ae8..8f9ce03b0 100644 --- a/client/apps/game/src/hooks/helpers/use-trade.tsx +++ b/client/apps/game/src/hooks/helpers/use-trade.tsx @@ -1,9 +1,9 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntities } from "@/hooks/helpers/use-entities"; +import { usePlayerRealms } from "@/hooks/helpers/use-entities"; import useUIStore from "@/hooks/store/use-ui-store"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; import { getRealmNameById } from "@/ui/utils/realms"; -import { ID, MarketInterface, Resource, ResourcesIds } from "@bibliothecadao/eternum"; +import { ContractAddress, ID, MarketInterface, Resource, ResourcesIds } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; import { Entity, HasValue, getComponentValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; @@ -160,13 +160,14 @@ export function useGetMyOffers(): MarketInterface[] { export function useSetMarket() { const { + account: { account }, setup: { components: { Status, Trade }, }, } = useDojo(); - const { playerRealms } = useEntities(); - const realmEntityIds = playerRealms().map((realm: any) => realm.entity_id); + const playerRealms = usePlayerRealms(ContractAddress(account.address)); + const { nextBlockTimestamp } = useNextBlockTimestamp(); const { computeTrades } = useTrade(); @@ -177,7 +178,7 @@ export function useSetMarket() { }, [allMarket]); const userTrades = useMemo(() => { - return allTrades.filter((trade) => realmEntityIds.includes(trade.makerId)); + return allTrades.filter((trade) => playerRealms.map((realm) => realm.entity_id).includes(trade.makerId)); }, [allTrades]); const bidOffers = useMemo(() => { diff --git a/client/apps/game/src/hooks/use-starting-tutorial.tsx b/client/apps/game/src/hooks/use-starting-tutorial.tsx index 8070cfbf8..c133b55a7 100644 --- a/client/apps/game/src/hooks/use-starting-tutorial.tsx +++ b/client/apps/game/src/hooks/use-starting-tutorial.tsx @@ -1,14 +1,14 @@ -import { QuestStatus, useQuests } from "@/hooks/helpers/use-quests"; +import { useQuests } from "@/hooks/helpers/use-quests"; import useUIStore from "@/hooks/store/use-ui-store"; import { questSteps, useTutorial } from "@/hooks/use-tutorial"; -import { QuestType } from "@bibliothecadao/eternum"; +import { QuestStatus, QuestType } from "@bibliothecadao/eternum"; import { useEffect } from "react"; export const useStartingTutorial = () => { const { handleStart } = useTutorial(questSteps.get(QuestType.Settle)); const showBlankOverlay = useUIStore((state) => state.showBlankOverlay); - const { quests } = useQuests(); + const quests = useQuests(); const settleQuest = quests.find((quest) => quest.id === QuestType.Settle); const tutorialCompleted = localStorage.getItem("tutorial") === "completed"; diff --git a/client/apps/game/src/ui/components/bank/add-liquidity.tsx b/client/apps/game/src/ui/components/bank/add-liquidity.tsx index 1a15cc686..3606bdc06 100644 --- a/client/apps/game/src/ui/components/bank/add-liquidity.tsx +++ b/client/apps/game/src/ui/components/bank/add-liquidity.tsx @@ -1,6 +1,5 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntities } from "@/hooks/helpers/use-entities"; -import { useResourceBalance } from "@/hooks/helpers/use-resources"; +import { usePlayerStructures } from "@/hooks/helpers/use-entities"; import { useIsResourcesLocked } from "@/hooks/helpers/use-structures"; import { ConfirmationPopup } from "@/ui/components/bank/confirmation-popup"; import { LiquidityResourceRow } from "@/ui/components/bank/liquidity-resource-row"; @@ -9,6 +8,7 @@ import { ResourceBar } from "@/ui/components/bank/resource-bar"; import Button from "@/ui/elements/button"; import { ResourceCost } from "@/ui/elements/resource-cost"; import { divideByPrecision, multiplyByPrecision } from "@/ui/utils/utils"; +import { getBalance } from "@/utils/resources"; import { ContractAddress, ID, MarketManager, ResourcesIds, resources } from "@bibliothecadao/eternum"; import { useEffect, useMemo, useState } from "react"; @@ -26,11 +26,9 @@ const AddLiquidity = ({ setup, } = useDojo(); - const { getBalance } = useResourceBalance(); + const playerStructures = usePlayerStructures(ContractAddress(account.address)); - const { playerStructures } = useEntities(); - - const playerStructureIds = playerStructures().map((structure) => structure.entity_id); + const playerStructureIds = playerStructures.map((structure) => structure.entity_id); const [isLoading, setIsLoading] = useState(false); const [resourceId, setResourceId] = useState(ResourcesIds.Wood); @@ -63,8 +61,8 @@ const AddLiquidity = ({ } }, [resourceAmount]); - const lordsBalance = getBalance(entityId, Number(ResourcesIds.Lords)).balance; - const resourceBalance = getBalance(entityId, Number(resourceId)).balance; + const lordsBalance = getBalance(entityId, Number(ResourcesIds.Lords), setup.components).balance; + const resourceBalance = getBalance(entityId, Number(resourceId), setup.components).balance; const hasEnough = lordsBalance >= multiplyByPrecision(lordsAmount) && resourceBalance >= multiplyByPrecision(resourceAmount); diff --git a/client/apps/game/src/ui/components/bank/liquidity-table.tsx b/client/apps/game/src/ui/components/bank/liquidity-table.tsx index af73d6ed2..9558bef61 100644 --- a/client/apps/game/src/ui/components/bank/liquidity-table.tsx +++ b/client/apps/game/src/ui/components/bank/liquidity-table.tsx @@ -1,6 +1,7 @@ -import { useEntities } from "@/hooks/helpers/use-entities"; +import { useDojo } from "@/hooks/context/dojo-context"; +import { usePlayerStructures } from "@/hooks/helpers/use-entities"; import { LiquidityResourceRow } from "@/ui/components/bank/liquidity-resource-row"; -import { ID, RESOURCE_TIERS, ResourcesIds, resources } from "@bibliothecadao/eternum"; +import { ContractAddress, ID, RESOURCE_TIERS, ResourcesIds, resources } from "@bibliothecadao/eternum"; import { useState } from "react"; type LiquidityTableProps = { @@ -20,6 +21,10 @@ export const LiquidityTableHeader = () => ( ); export const LiquidityTable = ({ bankEntityId, entity_id }: LiquidityTableProps) => { + const { + account: { account }, + } = useDojo(); + const [searchTerm, setSearchTerm] = useState(""); if (!bankEntityId) { @@ -37,9 +42,9 @@ export const LiquidityTable = ({ bankEntityId, entity_id }: LiquidityTableProps) ); }); - const { playerStructures } = useEntities(); + const playerStructures = usePlayerStructures(ContractAddress(account.address)); - const playerStructureIds = playerStructures().map((structure) => structure.entity_id); + const playerStructureIds = playerStructures.map((structure) => structure.entity_id); return (
diff --git a/client/apps/game/src/ui/components/bank/resource-bar.tsx b/client/apps/game/src/ui/components/bank/resource-bar.tsx index 4e0137670..36014bab6 100644 --- a/client/apps/game/src/ui/components/bank/resource-bar.tsx +++ b/client/apps/game/src/ui/components/bank/resource-bar.tsx @@ -1,10 +1,11 @@ -import { useResourceBalance } from "@/hooks/helpers/use-resources"; +import { useDojo } from "@/hooks/context/dojo-context"; import { HintSection } from "@/ui/components/hints/hint-modal"; import { NumberInput } from "@/ui/elements/number-input"; import { ResourceCost } from "@/ui/elements/resource-cost"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/ui/elements/select"; import TextInput from "@/ui/elements/text-input"; import { divideByPrecision, formatNumber } from "@/ui/utils/utils"; +import { getBalance } from "@/utils/resources"; import { ID, Resources, ResourcesIds, findResourceById, findResourceIdByTrait } from "@bibliothecadao/eternum"; import { memo, useEffect, useRef, useState } from "react"; @@ -34,7 +35,7 @@ export const ResourceBar = memo( onBlur?: () => void; // New prop max?: number; }) => { - const { getBalance } = useResourceBalance(); + const dojo = useDojo(); const [selectedResourceBalance, setSelectedResourceBalance] = useState(0); const [searchInput, setSearchInput] = useState(""); @@ -43,7 +44,9 @@ export const ResourceBar = memo( const inputRef = useRef(null); useEffect(() => { - setSelectedResourceBalance(divideByPrecision(getBalance(entityId, Number(resourceId)).balance)); + setSelectedResourceBalance( + divideByPrecision(getBalance(entityId, Number(resourceId), dojo.setup.components).balance), + ); }, [resourceId, getBalance, entityId]); const handleResourceChange = (trait: string) => { @@ -145,7 +148,7 @@ export const ResourceBar = memo( diff --git a/client/apps/game/src/ui/components/bank/swap.tsx b/client/apps/game/src/ui/components/bank/swap.tsx index c5e7e149c..eb6fe63b3 100644 --- a/client/apps/game/src/ui/components/bank/swap.tsx +++ b/client/apps/game/src/ui/components/bank/swap.tsx @@ -1,7 +1,6 @@ import { ReactComponent as Refresh } from "@/assets/icons/common/refresh.svg"; import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useResourceBalance } from "@/hooks/helpers/use-resources"; import { useIsResourcesLocked, useStructures } from "@/hooks/helpers/use-structures"; import { useTravel } from "@/hooks/helpers/use-travel"; import { soundSelector, useUiSounds } from "@/hooks/use-ui-sound"; @@ -11,6 +10,7 @@ import { TravelInfo } from "@/ui/components/resources/travel-info"; import Button from "@/ui/elements/button"; import { ResourceIcon } from "@/ui/elements/resource-icon"; import { divideByPrecision, formatNumber, multiplyByPrecision } from "@/ui/utils/utils"; +import { getBalance } from "@/utils/resources"; import { ContractAddress, DONKEY_ENTITY_TYPE, @@ -37,7 +37,6 @@ export const ResourceSwap = ({ setup, } = useDojo(); - const { getBalance } = useResourceBalance(); const { computeTravelTime } = useTravel(); const { play: playLordsSound } = useUiSounds(soundSelector.addLords); @@ -76,8 +75,14 @@ export const ResourceSwap = ({ } }, [marketManager.resourceId]); - const lordsBalance = useMemo(() => getBalance(entityId, ResourcesIds.Lords).balance, [entityId, getBalance]); - const resourceBalance = useMemo(() => getBalance(entityId, resourceId).balance, [entityId, resourceId, getBalance]); + const lordsBalance = useMemo( + () => getBalance(entityId, ResourcesIds.Lords, setup.components).balance, + [entityId, getBalance], + ); + const resourceBalance = useMemo( + () => getBalance(entityId, resourceId, setup.components).balance, + [entityId, resourceId, getBalance], + ); const hasEnough = useMemo(() => { const amount = isBuyResource ? lordsAmount + ownerFee : resourceAmount; diff --git a/client/apps/game/src/ui/components/battles/battle-list-item.tsx b/client/apps/game/src/ui/components/battles/battle-list-item.tsx index 6f419fc13..fd51c8589 100644 --- a/client/apps/game/src/ui/components/battles/battle-list-item.tsx +++ b/client/apps/game/src/ui/components/battles/battle-list-item.tsx @@ -2,12 +2,12 @@ import { ReactComponent as Inventory } from "@/assets/icons/common/bagpack.svg"; import { ReactComponent as Sword } from "@/assets/icons/common/cross-swords.svg"; import { ReactComponent as Eye } from "@/assets/icons/common/eye.svg"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import useUIStore from "@/hooks/store/use-ui-store"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; import { ViewOnMapIcon } from "@/ui/components/military/army-management-card"; import { TroopDisplay } from "@/ui/components/military/troop-chip"; import { InventoryResources } from "@/ui/components/resources/inventory-resources"; +import { getAddressNameFromEntity } from "@/utils/entities"; import { ArmyInfo, BattleManager, ID } from "@bibliothecadao/eternum"; import React, { useMemo, useState } from "react"; @@ -20,8 +20,6 @@ type BattleListItemProps = { export const BattleListItem = ({ battleEntityId, ownArmySelected, showCompass = false }: BattleListItemProps) => { const dojo = useDojo(); - const { getAddressNameFromEntity } = useEntitiesUtils(); - const { nextBlockTimestamp } = useNextBlockTimestamp(); const [showInventory, setShowInventory] = useState(false); @@ -107,7 +105,7 @@ export const BattleListItem = ({ battleEntityId, ownArmySelected, showCompass = onMouseEnter={() => setTooltip({ content: armiesInBattle.map((armyEntityId) => { - const name = getAddressNameFromEntity(armyEntityId); + const name = getAddressNameFromEntity(armyEntityId, dojo.setup.components); return
{name ? name : "Bandit"}
; }), position: "top", diff --git a/client/apps/game/src/ui/components/cityview/realm/settle-realm-component.tsx b/client/apps/game/src/ui/components/cityview/realm/settle-realm-component.tsx index ac1784b60..fab10c1e3 100644 --- a/client/apps/game/src/ui/components/cityview/realm/settle-realm-component.tsx +++ b/client/apps/game/src/ui/components/cityview/realm/settle-realm-component.tsx @@ -1,98 +1,13 @@ import { ReactComponent as CheckboxChecked } from "@/assets/icons/checkbox-checked.svg"; import { ReactComponent as CheckboxUnchecked } from "@/assets/icons/checkbox-unchecked.svg"; -import { useDojo } from "@/hooks/context/dojo-context"; -import { RealmInfo, usePlayerRealms } from "@/hooks/helpers/use-realm"; -import Button from "@/ui/elements/button"; import { ResourceIcon } from "@/ui/elements/resource-icon"; import { unpackResources } from "@/ui/utils/packed-data"; import { getRealm } from "@/ui/utils/realms"; -import { RealmInterface, ResourcesIds } from "@bibliothecadao/eternum"; +import { RealmInfo, RealmInterface, ResourcesIds } from "@bibliothecadao/eternum"; import { gql } from "graphql-request"; -import { useEffect, useState } from "react"; import { addAddressPadding } from "starknet"; import { env } from "../../../../../env"; -const SettleRealmComponent = ({ setSettledRealmId }: { setSettledRealmId: (id: number) => void }) => { - const { - account: { account }, - setup: { - systemCalls: { create_multiple_realms }, - }, - } = useDojo(); - - const [loading, setLoading] = useState(false); - const [selectedRealms, setSelectedRealms] = useState([]); - - const [seasonPassRealms, setSeasonPassRealms] = useState([]); - - const settleRealms = async (realmIds: number[]) => { - setLoading(true); - try { - const res = await create_multiple_realms({ - realm_ids: realmIds, - owner: account.address, - frontend: env.VITE_PUBLIC_CLIENT_FEE_RECIPIENT, - signer: account, - season_pass_address: env.VITE_SEASON_PASS_ADDRESS, - }); - } catch (error) { - console.error("Error settling realms:", error); - setLoading(false); - } - }; - - const realms = usePlayerRealms(); - - useEffect(() => { - getUnusedSeasonPasses(account.address, realms).then((unsettledSeasonPassRealms) => { - if (unsettledSeasonPassRealms.length !== seasonPassRealms.length) { - setSeasonPassRealms(unsettledSeasonPassRealms); - setLoading(false); - } - }); - }, [loading, realms]); - - return ( -
-
-

Settle Realms

-
- -
-
- {seasonPassRealms.map((realm) => ( - - setSelectedRealms( - selected ? [...selectedRealms, realm.realmId] : selectedRealms.filter((id) => id !== realm.realmId), - ) - } - className={`col-start-1`} - /> - ))} -
-
- -
-
-
- ); -}; - const querySeasonPasses = async (accountAddress: string) => { const getAccountTokens = gql` query getAccountTokens($accountAddress: String!) { diff --git a/client/apps/game/src/ui/components/construction/select-preview-building.tsx b/client/apps/game/src/ui/components/construction/select-preview-building.tsx index 4b84dd591..f92daacf5 100644 --- a/client/apps/game/src/ui/components/construction/select-preview-building.tsx +++ b/client/apps/game/src/ui/components/construction/select-preview-building.tsx @@ -1,7 +1,5 @@ import { configManager } from "@/dojo/setup"; import { DojoResult, useDojo } from "@/hooks/context/dojo-context"; -import { useGetRealm } from "@/hooks/helpers/use-realm"; -import { useResourceBalance } from "@/hooks/helpers/use-resources"; import useUIStore from "@/hooks/store/use-ui-store"; import { usePlayResourceSound } from "@/hooks/use-ui-sound"; import { ResourceMiningTypes } from "@/types"; @@ -22,6 +20,8 @@ import { gramToKg, isResourceProductionBuilding, } from "@/ui/utils/utils"; +import { getRealmInfo } from "@/utils/realm"; +import { getBalance } from "@/utils/resources"; import { BuildingEnumToString, BuildingType, @@ -44,9 +44,8 @@ export const SelectPreviewBuildingMenu = ({ className, entityId }: { className?: const setPreviewBuilding = useUIStore((state) => state.setPreviewBuilding); const previewBuilding = useUIStore((state) => state.previewBuilding); - const { realm } = useGetRealm(entityId); + const realm = getRealmInfo(getEntityIdFromKeys([BigInt(entityId)]), dojo.setup.components); - const { getBalance } = useResourceBalance(); const { playResourceSound } = usePlayResourceSound(); const buildingTypes = Object.keys(BuildingType).filter( @@ -75,7 +74,7 @@ export const SelectPreviewBuildingMenu = ({ className, entityId }: { className?: const checkBalance = (cost: any) => Object.keys(cost).every((resourceId) => { const resourceCost = cost[Number(resourceId)]; - const balance = getBalance(entityId, resourceCost.resource); + const balance = getBalance(entityId, resourceCost.resource, dojo.setup.components); return divideByPrecision(balance.balance) >= resourceCost.amount; }); @@ -234,7 +233,7 @@ export const SelectPreviewBuildingMenu = ({ className, entityId }: { className?: const hasBalance = checkBalance(buildingCost); const hasEnoughPopulation = hasEnoughPopulationForBuilding(realm, building); - const canBuild = hasBalance && realm.hasCapacity && hasEnoughPopulation; + const canBuild = hasBalance && realm?.hasCapacity && hasEnoughPopulation; const isBarracks = building === BuildingType.Barracks; const isArcheryRange = building === BuildingType.ArcheryRange; @@ -274,7 +273,7 @@ export const SelectPreviewBuildingMenu = ({ className, entityId }: { className?: ), }, ], - [realm, entityId, realmResourceIds, selectedTab, previewBuilding, playResourceSound, realm.population], + [realm, entityId, realmResourceIds, selectedTab, previewBuilding, playResourceSound], ); return ( @@ -408,8 +407,6 @@ export const ResourceInfo = ({ const amountProducedPerTick = divideByPrecision(configManager.getResourceOutputs(resourceId)); - const { getBalance } = useResourceBalance(); - const consumedBy = useMemo(() => { return getConsumedBy(resourceId); }, [resourceId]); @@ -461,7 +458,7 @@ export const ResourceInfo = ({
consumed per/s
{Object.keys(cost).map((resourceId) => { - const balance = getBalance(entityId || 0, cost[Number(resourceId)].resource); + const balance = getBalance(entityId || 0, cost[Number(resourceId)].resource, dojo.setup.components); return ( {Object.keys(buildingCost).map((resourceId, index) => { - const balance = getBalance(entityId || 0, buildingCost[Number(resourceId)].resource); + const balance = getBalance(entityId || 0, buildingCost[Number(resourceId)].resource, dojo.setup.components); return ( { return getConsumedBy(resourceProduced); }, [resourceProduced]); @@ -601,7 +596,11 @@ export const BuildingInfo = ({
{resourceProduced !== 0 && Object.keys(ongoingCost).map((resourceId, index) => { - const balance = getBalance(entityId || 0, ongoingCost[Number(resourceId)].resource); + const balance = getBalance( + entityId || 0, + ongoingCost[Number(resourceId)].resource, + dojo.setup.components, + ); return ( One Time Cost
{Object.keys(buildingCost).map((resourceId, index) => { - const balance = getBalance(entityId || 0, buildingCost[Number(resourceId)].resource); + const balance = getBalance( + entityId || 0, + buildingCost[Number(resourceId)].resource, + dojo.setup.components, + ); return ( ; -const CACHE_KEY = "inventory-resources-sync"; -const CACHE_DURATION = 2 * 60 * 1000; // 2 minutes in milliseconds - export const EntityArrival = ({ arrival, ...props }: EntityProps) => { const dojo = useDojo(); - const { getEntityInfo, getEntityName } = useEntitiesUtils(); - const { getResourcesFromBalance } = useResourcesUtils(); + const components = dojo.setup.components; + const { nextBlockTimestamp } = useNextBlockTimestamp(); const { getArmy } = useGetArmyByEntityId(); const weight = useComponentValue(dojo.setup.components.Weight, getEntityIdFromKeys([BigInt(arrival.entityId)])); - const entity = getEntityInfo(arrival.entityId); + const entity = getEntityInfo(arrival.entityId, ContractAddress(dojo.account.account.address), dojo.setup.components); const entityResources = useMemo(() => { - return getResourcesFromBalance(arrival.entityId); + return getResourcesFromBalance(arrival.entityId, components); }, [weight]); const army = useMemo(() => getArmy(arrival.entityId), [arrival.entityId, entity.resources]); @@ -54,12 +50,12 @@ export const EntityArrival = ({ arrival, ...props }: EntityProps) => { return nextBlockTimestamp ? ( arrival.arrivesAt <= nextBlockTimestamp ? (
- Waiting to offload to {getEntityName(arrival.recipientEntityId)} + Waiting to offload to {getEntityName(arrival.recipientEntityId, components)}
) : (
Arriving in {formatTime(Number(entity.arrivalTime) - nextBlockTimestamp)} to{" "} - {getEntityName(arrival.recipientEntityId)} + {getEntityName(arrival.recipientEntityId, components)}
) ) : null; diff --git a/client/apps/game/src/ui/components/fragmentMines/fragment-mine-panel.tsx b/client/apps/game/src/ui/components/fragmentMines/fragment-mine-panel.tsx index cbe08243d..a5d973559 100644 --- a/client/apps/game/src/ui/components/fragmentMines/fragment-mine-panel.tsx +++ b/client/apps/game/src/ui/components/fragmentMines/fragment-mine-panel.tsx @@ -1,13 +1,14 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import Button from "@/ui/elements/button"; import TextInput from "@/ui/elements/text-input"; +import { getAddressNameFromEntity } from "@/utils/entities"; import { MAX_NAME_LENGTH } from "@bibliothecadao/eternum"; import { useState } from "react"; export const FragmentMinePanel = ({ entity }: any) => { const { account: { account }, + setup: { components }, network: { provider }, } = useDojo(); @@ -15,8 +16,7 @@ export const FragmentMinePanel = ({ entity }: any) => { const [editName, setEditName] = useState(false); const [naming, setNaming] = useState(""); - const { getAddressNameFromEntity } = useEntitiesUtils(); - const ownerName = getAddressNameFromEntity(entity.entity_id); + const ownerName = getAddressNameFromEntity(entity.entity_id, components); return (
diff --git a/client/apps/game/src/ui/components/hyperstructures/co-owners.tsx b/client/apps/game/src/ui/components/hyperstructures/co-owners.tsx index 5fe34d3ec..6068fe5bb 100644 --- a/client/apps/game/src/ui/components/hyperstructures/co-owners.tsx +++ b/client/apps/game/src/ui/components/hyperstructures/co-owners.tsx @@ -1,7 +1,6 @@ import { ReactComponent as Trash } from "@/assets/icons/common/trashcan.svg"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useGetAllPlayers } from "@/hooks/helpers/use-get-all-players"; -import { useRealm } from "@/hooks/helpers/use-realm"; +import { usePlayers } from "@/hooks/helpers/use-players"; import { useStructureByEntityId } from "@/hooks/helpers/use-structures"; import useUIStore from "@/hooks/store/use-ui-store"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; @@ -11,6 +10,7 @@ import { SelectAddress } from "@/ui/elements/select-address"; import { SortButton, SortInterface } from "@/ui/elements/sort-button"; import { SortPanel } from "@/ui/elements/sort-panel"; import { displayAddress, formatTime } from "@/ui/utils/utils"; +import { getAddressName } from "@/utils/entities"; import { ContractAddress, HYPERSTRUCTURE_CONFIG_ID, ID } from "@bibliothecadao/eternum"; import { useComponentValue } from "@dojoengine/react"; import { getComponentValue } from "@dojoengine/recs"; @@ -54,10 +54,10 @@ const CoOwnersRows = ({ }) => { const { account: { account }, - setup: { - components: { Hyperstructure, HyperstructureConfig }, - }, + setup: { components }, } = useDojo(); + const { Hyperstructure, HyperstructureConfig } = components; + const setTooltip = useUIStore((state) => state.setTooltip); const { nextBlockTimestamp } = useNextBlockTimestamp(); @@ -82,8 +82,6 @@ const CoOwnersRows = ({ const structure = useStructureByEntityId(hyperstructureEntityId); - const { getAddressName } = useRealm(); - const sortingParams = useMemo(() => { return [ { label: "Name", sortKey: "name", className: "" }, @@ -118,7 +116,7 @@ const CoOwnersRows = ({ {coOwnersWithTimestamp?.coOwners.map((coOwner, index) => { - const playerName = getAddressName(coOwner.address) || "Player not found"; + const playerName = getAddressName(coOwner.address, components) || "Player not found"; const isOwner = coOwner.address === ContractAddress(account.address); @@ -178,7 +176,7 @@ const ChangeCoOwners = ({ }, } = useDojo(); - const getPlayers = useGetAllPlayers(); + const players = usePlayers(); const [isLoading, setIsLoading] = useState(false); const [newCoOwners, setNewCoOwners] = useState< { @@ -237,10 +235,6 @@ const ChangeCoOwners = ({ .some((coOwner) => coOwner.address === ContractAddress(account.address)); }, [newCoOwners, account.address]); - const players = useMemo(() => { - return getPlayers(); - }, []); - return (
diff --git a/client/apps/game/src/ui/components/hyperstructures/contribution-summary.tsx b/client/apps/game/src/ui/components/hyperstructures/contribution-summary.tsx index 8babd5b3f..6f471912d 100644 --- a/client/apps/game/src/ui/components/hyperstructures/contribution-summary.tsx +++ b/client/apps/game/src/ui/components/hyperstructures/contribution-summary.tsx @@ -1,9 +1,9 @@ -import { useContributions } from "@/hooks/helpers/use-contributions"; -import { useRealm } from "@/hooks/helpers/use-realm"; +import { useDojo } from "@/hooks/context/dojo-context"; import { ResourceIcon } from "@/ui/elements/resource-icon"; import { SelectResource } from "@/ui/elements/select-resource"; import { copyPlayerAddressToClipboard, currencyIntlFormat, divideByPrecision, formatNumber } from "@/ui/utils/utils"; -import { ContractAddress, ID, ResourcesIds } from "@bibliothecadao/eternum"; +import { getAddressName } from "@/utils/entities"; +import { ContractAddress, ID, LeaderboardManager, ResourcesIds } from "@bibliothecadao/eternum"; import { useMemo, useState } from "react"; export const ContributionSummary = ({ @@ -13,8 +13,13 @@ export const ContributionSummary = ({ hyperstructureEntityId: ID; className?: string; }) => { - const { getContributions, getContributionsTotalPercentage } = useContributions(); - const { getAddressName } = useRealm(); + const { + setup: { components }, + } = useDojo(); + + const leaderboardManager = useMemo(() => { + return LeaderboardManager.instance(components); + }, [components]); const [showContributions, setShowContributions] = useState(false); const [selectedResource, setSelectedResource] = useState(null); @@ -24,7 +29,10 @@ export const ContributionSummary = ({ resourceId: number; }; - const contributions = getContributions(hyperstructureEntityId); + const contributions = useMemo(() => { + return leaderboardManager.getContributions(hyperstructureEntityId); + }, [leaderboardManager, hyperstructureEntityId]); + const groupedContributions = contributions.reduce>>((acc, contribution) => { const { player_address, resource_type, amount } = contribution; const playerAddressString = player_address.toString(); @@ -59,7 +67,10 @@ export const ContributionSummary = ({ playerAddress, resources, percentage: - getContributionsTotalPercentage(hyperstructureEntityId, resourceContributions[playerAddress]) * 100, + leaderboardManager.getContributionsTotalPercentage( + hyperstructureEntityId, + resourceContributions[playerAddress], + ) * 100, })) .filter(({ resources }) => selectedResource ? resources[selectedResource] > 0n : Object.values(resources).some((amount) => amount > 0n), @@ -89,7 +100,7 @@ export const ContributionSummary = ({ setSelectedResource(resourceId)} />
{sortedContributors.map(({ playerAddress, resources, percentage }) => { - const addressName = getAddressName(ContractAddress(playerAddress)) || "Unknown"; + const addressName = getAddressName(ContractAddress(playerAddress), components) || "Unknown"; return (
diff --git a/client/apps/game/src/ui/components/hyperstructures/hyperstructure-panel.tsx b/client/apps/game/src/ui/components/hyperstructures/hyperstructure-panel.tsx index 62aacdbe1..0080b563b 100644 --- a/client/apps/game/src/ui/components/hyperstructures/hyperstructure-panel.tsx +++ b/client/apps/game/src/ui/components/hyperstructures/hyperstructure-panel.tsx @@ -1,7 +1,6 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useContributions } from "@/hooks/helpers/use-contributions"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; +import { usePlayerContributions } from "@/hooks/helpers/use-contributions"; import { useGuilds } from "@/hooks/helpers/use-guilds"; import { ProgressWithPercentage, @@ -17,6 +16,7 @@ import Button from "@/ui/elements/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/ui/elements/select"; import TextInput from "@/ui/elements/text-input"; import { currencyIntlFormat, getEntityIdFromKeys, multiplyByPrecision, separateCamelCase } from "@/ui/utils/utils"; +import { getAddressNameFromEntity } from "@/utils/entities"; import { Access, calculateCompletionPoints, @@ -48,7 +48,7 @@ export const HyperstructurePanel = ({ entity }: any) => { network: { provider }, setup: { systemCalls: { contribute_to_construction, set_access }, - components: { Hyperstructure }, + components, }, } = dojo; @@ -66,18 +66,15 @@ export const HyperstructurePanel = ({ entity }: any) => { const progresses = useHyperstructureProgress(entity.entity_id); - const { useContributionsByPlayerAddress } = useContributions(); - - const myContributions = useContributionsByPlayerAddress(BigInt(account.address), entity.entity_id); + const myContributions = usePlayerContributions(BigInt(account.address), entity.entity_id); const updates = useHyperstructureUpdates(entity.entity_id); const [newContributions, setNewContributions] = useState>({}); - const { getAddressNameFromEntity } = useEntitiesUtils(); - const ownerName = getAddressNameFromEntity(entity.entity_id); + const ownerName = getAddressNameFromEntity(entity.entity_id, components); - const hyperstructure = useComponentValue(Hyperstructure, getEntityIdFromKeys([BigInt(entity.entity_id)])); + const hyperstructure = useComponentValue(components.Hyperstructure, getEntityIdFromKeys([BigInt(entity.entity_id)])); const playerGuild = useMemo(() => getGuildFromPlayerAddress(ContractAddress(account.address)), []); diff --git a/client/apps/game/src/ui/components/hyperstructures/hyperstructure-resource-chip.tsx b/client/apps/game/src/ui/components/hyperstructures/hyperstructure-resource-chip.tsx index f39aa54c5..1a8efc3f6 100644 --- a/client/apps/game/src/ui/components/hyperstructures/hyperstructure-resource-chip.tsx +++ b/client/apps/game/src/ui/components/hyperstructures/hyperstructure-resource-chip.tsx @@ -1,10 +1,11 @@ +import { useDojo } from "@/hooks/context/dojo-context"; import { ProgressWithPercentage } from "@/hooks/helpers/use-hyperstructures"; -import { useResourceBalance } from "@/hooks/helpers/use-resources"; import useUIStore from "@/hooks/store/use-ui-store"; import Button from "@/ui/elements/button"; import { NumberInput } from "@/ui/elements/number-input"; import { ResourceIcon } from "@/ui/elements/resource-icon"; import { currencyIntlFormat, divideByPrecision } from "@/ui/utils/utils"; +import { getBalance, getResourceProductionInfo } from "@/utils/resources"; import { findResourceById, getIconResourceId, ID } from "@bibliothecadao/eternum"; import { useEffect, useState } from "react"; @@ -25,12 +26,13 @@ export const HyperstructureResourceChip = ({ progress, resetContributions, }: HyperstructureResourceChipProps) => { + const dojo = useDojo(); + const [inputValue, setInputValue] = useState(0); const setTooltip = useUIStore((state) => state.setTooltip); - const { getBalance, getResourceProductionInfo } = useResourceBalance(); - const balance = divideByPrecision(getBalance(structureEntityId, resourceId).balance); - const production = getResourceProductionInfo(structureEntityId, resourceId); + const balance = divideByPrecision(getBalance(structureEntityId, resourceId, dojo.setup.components).balance); + const production = getResourceProductionInfo(structureEntityId, resourceId, dojo.setup.components); const safetyMargin = production !== undefined && production?.consumption_rate !== 0n ? 0.95 : 1; diff --git a/client/apps/game/src/ui/components/hyperstructures/leaderboard.tsx b/client/apps/game/src/ui/components/hyperstructures/leaderboard.tsx index 5037ce25a..7da7147f8 100644 --- a/client/apps/game/src/ui/components/hyperstructures/leaderboard.tsx +++ b/client/apps/game/src/ui/components/hyperstructures/leaderboard.tsx @@ -1,11 +1,11 @@ import { useDojo } from "@/hooks/context/dojo-context"; import { useHyperstructureUpdates } from "@/hooks/helpers/use-hyperstructures"; -import { useRealm } from "@/hooks/helpers/use-realm"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; import Button from "@/ui/elements/button"; import { SortButton, SortInterface } from "@/ui/elements/sort-button"; import { SortPanel } from "@/ui/elements/sort-panel"; import { currencyIntlFormat, displayAddress, getEntityIdFromKeys } from "@/ui/utils/utils"; +import { getAddressName } from "@/utils/entities"; import { ContractAddress, ID, LeaderboardManager } from "@bibliothecadao/eternum"; import { getComponentValue } from "@dojoengine/recs"; import { useMemo, useState } from "react"; @@ -20,15 +20,11 @@ export const Leaderboard = ({ const dojo = useDojo(); const { account: { account }, - setup: { - components: { Owner }, - }, + setup: { components }, } = dojo; const { nextBlockTimestamp } = useNextBlockTimestamp(); - const { getAddressName } = useRealm(); - const playerPointsLeaderboard = useMemo(() => { return LeaderboardManager.instance(dojo.setup.components).getPlayersByRank( nextBlockTimestamp || 0, @@ -52,7 +48,7 @@ export const Leaderboard = ({ }); const isOwner = useMemo(() => { - const owner = getComponentValue(Owner, getEntityIdFromKeys([BigInt(hyperstructureEntityId)])); + const owner = getComponentValue(components.Owner, getEntityIdFromKeys([BigInt(hyperstructureEntityId)])); if (!owner) return false; return ContractAddress(owner.address) === ContractAddress(account.address); }, [hyperstructureEntityId]); @@ -77,7 +73,7 @@ export const Leaderboard = ({ ))} {playerPointsLeaderboard.map(([address, points], index) => { - const playerName = getAddressName(address) || "Player not found"; + const playerName = getAddressName(address, components) || "Player not found"; const isOwner = address === ContractAddress(account.address); diff --git a/client/apps/game/src/ui/components/hyperstructures/resource-exchange.tsx b/client/apps/game/src/ui/components/hyperstructures/resource-exchange.tsx index 1082ae89a..1a865ff11 100644 --- a/client/apps/game/src/ui/components/hyperstructures/resource-exchange.tsx +++ b/client/apps/game/src/ui/components/hyperstructures/resource-exchange.tsx @@ -1,9 +1,9 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useResourcesUtils } from "@/hooks/helpers/use-resources"; import Button from "@/ui/elements/button"; import { NumberInput } from "@/ui/elements/number-input"; import { ResourceIcon } from "@/ui/elements/resource-icon"; import { currencyFormat, divideByPrecision, multiplyByPrecision } from "@/ui/utils/utils"; +import { getResourcesFromBalance } from "@/utils/resources"; import { ArmyInfo, ID, ResourcesIds } from "@bibliothecadao/eternum"; import { ArrowRight } from "lucide-react"; import { useMemo, useState } from "react"; @@ -25,13 +25,12 @@ export const ResourceExchange = ({ }: ResourceExchangeProps) => { const { setup: { + components, account: { account }, systemCalls: { send_resources }, }, } = useDojo(); - const { getResourcesFromBalance } = useResourcesUtils(); - const [loading, setLoading] = useState(false); const [transferDirection, setTransferDirection] = useState<"to" | "from">("to"); const [resourcesGiven, setResourcesGiven] = useState>( @@ -46,8 +45,8 @@ export const ResourceExchange = ({ ), ); - const giverArmyResources = useMemo(() => getResourcesFromBalance(giverArmyEntityId), [loading]); - const takerArmyResources = useMemo(() => getResourcesFromBalance(takerArmy?.entity_id!), [loading]); + const giverArmyResources = useMemo(() => getResourcesFromBalance(giverArmyEntityId, components), [loading]); + const takerArmyResources = useMemo(() => getResourcesFromBalance(takerArmy?.entity_id!, components), [loading]); const handleResourceGivenChange = (resourceId: number, amount: number) => { setResourcesGiven({ ...resourcesGiven, [resourceId]: amount }); diff --git a/client/apps/game/src/ui/components/military/army-chip.tsx b/client/apps/game/src/ui/components/military/army-chip.tsx index d510efe92..e04a0e952 100644 --- a/client/apps/game/src/ui/components/military/army-chip.tsx +++ b/client/apps/game/src/ui/components/military/army-chip.tsx @@ -4,7 +4,6 @@ import { ReactComponent as Swap } from "@/assets/icons/common/swap.svg"; import { ReactComponent as Compass } from "@/assets/icons/compass.svg"; import { useDojo } from "@/hooks/context/dojo-context"; import { useArmiesAtPosition } from "@/hooks/helpers/use-armies"; -import { armyHasTroops } from "@/hooks/helpers/use-quests"; import useUIStore from "@/hooks/store/use-ui-store"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; import { Position as PositionInterface } from "@/types/position"; @@ -15,6 +14,7 @@ import { Exchange } from "@/ui/components/structures/worldmap/structure-card"; import { ArmyCapacity } from "@/ui/elements/army-capacity"; import Button from "@/ui/elements/button"; import { StaminaResource } from "@/ui/elements/stamina-resource"; +import { armyHasTroops } from "@/utils/army"; import { ArmyInfo, BattleManager, Position } from "@bibliothecadao/eternum"; import { LucideArrowRight } from "lucide-react"; import React, { Dispatch, SetStateAction, useMemo, useState } from "react"; diff --git a/client/apps/game/src/ui/components/military/army-list.tsx b/client/apps/game/src/ui/components/military/army-list.tsx index 08ff164c8..be9aa7cef 100644 --- a/client/apps/game/src/ui/components/military/army-list.tsx +++ b/client/apps/game/src/ui/components/military/army-list.tsx @@ -1,14 +1,13 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; import { useArmiesByStructure } from "@/hooks/helpers/use-armies"; -import { type PlayerStructure } from "@/hooks/helpers/use-entities"; import useUIStore from "@/hooks/store/use-ui-store"; import { HintSection } from "@/ui/components/hints/hint-modal"; import { ArmyChip } from "@/ui/components/military/army-chip"; import Button from "@/ui/elements/button"; import { Headline } from "@/ui/elements/headline"; import { HintModalButton } from "@/ui/elements/hint-modal-button"; -import { BuildingType, StructureType, TileManager } from "@bibliothecadao/eternum"; +import { BuildingType, PlayerStructure, StructureType, TileManager } from "@bibliothecadao/eternum"; import { useMemo, useState } from "react"; const MAX_AMOUNT_OF_DEFENSIVE_ARMIES = 1; diff --git a/client/apps/game/src/ui/components/military/army-management-card.tsx b/client/apps/game/src/ui/components/military/army-management-card.tsx index 14631a175..3d446a238 100644 --- a/client/apps/game/src/ui/components/military/army-management-card.tsx +++ b/client/apps/game/src/ui/components/military/army-management-card.tsx @@ -4,7 +4,6 @@ import { ReactComponent as Map } from "@/assets/icons/common/world.svg"; import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; import { useQuery } from "@/hooks/helpers/use-query"; -import { useResourceBalance } from "@/hooks/helpers/use-resources"; import useUIStore from "@/hooks/store/use-ui-store"; import { Position as PositionInterface } from "@/types/position"; import Button from "@/ui/elements/button"; @@ -19,6 +18,7 @@ import { getEntityIdFromKeys, multiplyByPrecision, } from "@/ui/utils/utils"; +import { getBalance } from "@/utils/resources"; import { ArmyInfo, ArmyManager, ID, Position, ResourcesIds, U32_MAX } from "@bibliothecadao/eternum"; import { useComponentValue } from "@dojoengine/react"; import clsx from "clsx"; @@ -49,7 +49,6 @@ export const ArmyManagementCard = ({ owner_entity, army, setSelectedEntity }: Ar const isDefendingArmy = Boolean(army?.protectee); const [confirmDelete, setConfirmDelete] = useState(false); - const { getBalance } = useResourceBalance(); const [isLoading, setIsLoading] = useState(false); const [canCreate, setCanCreate] = useState(false); @@ -135,7 +134,7 @@ export const ArmyManagementCard = ({ owner_entity, army, setSelectedEntity }: Ar let canCreate = true; Object.keys(troopCounts).forEach((troopId) => { const count = troopCounts[Number(troopId)]; - const balance = getBalance(owner_entity, Number(troopId)).balance; + const balance = getBalance(owner_entity, Number(troopId), dojo.setup.components).balance; if (count > balance) { canCreate = false; } @@ -248,7 +247,7 @@ export const ArmyManagementCard = ({ owner_entity, army, setSelectedEntity }: Ar
{troops.map((troop) => { - const balance = getBalance(owner_entity, troop.name).balance; + const balance = getBalance(owner_entity, troop.name, dojo.setup.components).balance; return (
diff --git a/client/apps/game/src/ui/components/military/entities-army-table.tsx b/client/apps/game/src/ui/components/military/entities-army-table.tsx index 1255e8003..289225d03 100644 --- a/client/apps/game/src/ui/components/military/entities-army-table.tsx +++ b/client/apps/game/src/ui/components/military/entities-army-table.tsx @@ -1,5 +1,5 @@ import { useArmiesByStructure } from "@/hooks/helpers/use-armies"; -import { useEntities } from "@/hooks/helpers/use-entities"; +import { usePlayerStructures } from "@/hooks/helpers/use-entities"; import useUIStore from "@/hooks/store/use-ui-store"; import { HintSection } from "@/ui/components/hints/hint-modal"; import { ArmyChip } from "@/ui/components/military/army-chip"; @@ -14,7 +14,7 @@ import { divideByPrecisionFormatted } from "@/ui/utils/utils"; import { ArmyInfo, ID, ResourcesIds } from "@bibliothecadao/eternum"; export const EntitiesArmyTable = () => { - const { playerStructures } = useEntities(); + const playerStructures = usePlayerStructures(); const togglePopup = useUIStore((state) => state.togglePopup); return ( @@ -29,7 +29,7 @@ export const EntitiesArmyTable = () => {
- {playerStructures().map((entity: any, index: number) => { + {playerStructures.map((entity: any, index: number) => { return (
diff --git a/client/apps/game/src/ui/components/military/pillage-history.tsx b/client/apps/game/src/ui/components/military/pillage-history.tsx index 684dd8b6a..fb8ef417e 100644 --- a/client/apps/game/src/ui/components/military/pillage-history.tsx +++ b/client/apps/game/src/ui/components/military/pillage-history.tsx @@ -1,9 +1,9 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import { ResourceCost } from "@/ui/elements/resource-cost"; import TwitterShareButton from "@/ui/elements/twitter-share-button"; import { formatSocialText, twitterTemplates } from "@/ui/socials"; import { divideByPrecision, formatNumber, formatResources, formatTime } from "@/ui/utils/utils"; +import { getAddressNameFromEntity, getPlayerAddressFromEntity } from "@/utils/entities"; import { BattleSide, ClientComponents, ID, Resource, resources } from "@bibliothecadao/eternum"; import { ComponentValue, defineQuery, getComponentValue, HasValue, isComponentUpdate } from "@dojoengine/recs"; import { useEffect, useMemo, useState } from "react"; @@ -15,6 +15,7 @@ type PillageEvent = ComponentValue { const { setup: { + components, account: { account }, }, } = useDojo(); @@ -22,17 +23,15 @@ const PillageHistoryItem = ({ addressName, history }: { addressName: string; his const isSuccess = history.winner === BattleSide[BattleSide.Attack]; const formattedResources = useMemo(() => formatResources(history.pillaged_resources), [history.pillaged_resources]); - const { getPlayerAddressFromEntity, getAddressNameFromEntity } = useEntitiesUtils(); - const attackerIsPlayer = useMemo( - () => getPlayerAddressFromEntity(history.pillager_army_entity_id) === BigInt(account.address), + () => getPlayerAddressFromEntity(history.pillager_army_entity_id, components) === BigInt(account.address), [getPlayerAddressFromEntity, history.pillager_army_entity_id, account.address], ); const twitterText = useMemo(() => { if (isSuccess && formattedResources.length > 0 && attackerIsPlayer) { return formatSocialText(twitterTemplates.pillage, { - enemyName: getAddressNameFromEntity(history.pillaged_structure_entity_id) || "Unknown", + enemyName: getAddressNameFromEntity(history.pillaged_structure_entity_id, components) || "Unknown", addressName, resources: formattedResources .map( @@ -124,28 +123,28 @@ const PillageHistoryItem = ({ addressName, history }: { addressName: string; his export const PillageHistory = ({ structureId }: { structureId: ID }) => { const { - setup: { - components: { events }, - }, + setup: { components }, } = useDojo(); const [pillageHistory, setPillageHistory] = useState([]); - const { getAddressNameFromEntity } = useEntitiesUtils(); useEffect(() => { - const query = defineQuery([HasValue(events.BattlePillageData, { pillaged_structure_entity_id: structureId })], { - runOnInit: true, - }); + const query = defineQuery( + [HasValue(components.events.BattlePillageData, { pillaged_structure_entity_id: structureId })], + { + runOnInit: true, + }, + ); const subscription = query.update$.subscribe((update) => { - if (isComponentUpdate(update, events.BattlePillageData)) { - const event = getComponentValue(events.BattlePillageData, update.entity); + if (isComponentUpdate(update, components.events.BattlePillageData)) { + const event = getComponentValue(components.events.BattlePillageData, update.entity); setPillageHistory((prev) => [event!, ...prev]); } }); return () => subscription.unsubscribe(); - }, [events.BattlePillageData, structureId]); + }, [components.events.BattlePillageData, structureId]); return (
@@ -155,7 +154,7 @@ export const PillageHistory = ({ structureId }: { structureId: ID }) => { .sort((a, b) => b.timestamp - a.timestamp) .slice(0, 20) .map((history, index) => { - const addressName = getAddressNameFromEntity(history.pillager_army_entity_id); + const addressName = getAddressNameFromEntity(history.pillager_army_entity_id, components); return ; })}
diff --git a/client/apps/game/src/ui/components/quest/quest-details.ts b/client/apps/game/src/ui/components/quest/quest-details.ts index dfdb1dadc..d7e7ab357 100644 --- a/client/apps/game/src/ui/components/quest/quest-details.ts +++ b/client/apps/game/src/ui/components/quest/quest-details.ts @@ -1,5 +1,4 @@ -import { Prize } from "@/hooks/helpers/use-quests"; -import { QuestType } from "@bibliothecadao/eternum"; +import { Prize, QuestType } from "@bibliothecadao/eternum"; interface StaticQuestInfo { name: string; diff --git a/client/apps/game/src/ui/components/resources/deposit-resources.tsx b/client/apps/game/src/ui/components/resources/deposit-resources.tsx index 6d40ec089..aade33beb 100644 --- a/client/apps/game/src/ui/components/resources/deposit-resources.tsx +++ b/client/apps/game/src/ui/components/resources/deposit-resources.tsx @@ -1,11 +1,10 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { ArrivalInfo } from "@/hooks/helpers/use-resource-arrivals"; import { useStructureByEntityId } from "@/hooks/helpers/use-structures"; import useUIStore from "@/hooks/store/use-ui-store"; import { soundSelector, useUiSounds } from "@/hooks/use-ui-sound"; import Button from "@/ui/elements/button"; import { getEntityIdFromKeys } from "@/ui/utils/utils"; -import { BattleManager, ID, Resource, ResourceInventoryManager } from "@bibliothecadao/eternum"; +import { ArrivalInfo, BattleManager, ID, Resource, ResourceInventoryManager } from "@bibliothecadao/eternum"; import { useComponentValue } from "@dojoengine/react"; import { useMemo, useState } from "react"; diff --git a/client/apps/game/src/ui/components/resources/inventory-resources.tsx b/client/apps/game/src/ui/components/resources/inventory-resources.tsx index 94d3c1e1c..67ae4ddd0 100644 --- a/client/apps/game/src/ui/components/resources/inventory-resources.tsx +++ b/client/apps/game/src/ui/components/resources/inventory-resources.tsx @@ -1,8 +1,8 @@ import { debouncedAddToSubscription } from "@/dojo/debounced-queries"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useResourceBalance, useResourcesUtils } from "@/hooks/helpers/use-resources"; import { ResourceCost } from "@/ui/elements/resource-cost"; import { divideByPrecision } from "@/ui/utils/utils"; +import { getBalance, getInventoryResources } from "@/utils/resources"; import { ID, Resource, ResourcesIds } from "@bibliothecadao/eternum"; import { useMemo, useState } from "react"; @@ -27,15 +27,22 @@ export const InventoryResources = ({ const dojo = useDojo(); const [showAll, setShowAll] = useState(false); - const { useResourcesFromBalance } = useResourcesUtils(); - const { getBalance } = useResourceBalance(); - const inventoriesResources = useResourcesFromBalance(entityId); + const inventoriesResources = useMemo( + () => getInventoryResources(entityId, dojo.setup.components), + [entityId, dojo.setup.components], + ); const [isSyncing, setIsSyncing] = useState(false); const dynamicResources = useMemo( - () => dynamic.map((resourceId): Resource => ({ resourceId, amount: getBalance(entityId, resourceId).balance })), + () => + dynamic.map( + (resourceId): Resource => ({ + resourceId, + amount: getBalance(entityId, resourceId, dojo.setup.components).balance, + }), + ), [dynamic, entityId, getBalance], ); diff --git a/client/apps/game/src/ui/components/resources/realm-resources-io.tsx b/client/apps/game/src/ui/components/resources/realm-resources-io.tsx index f59e1c7ec..35dcc3857 100644 --- a/client/apps/game/src/ui/components/resources/realm-resources-io.tsx +++ b/client/apps/game/src/ui/components/resources/realm-resources-io.tsx @@ -1,8 +1,10 @@ import { configManager } from "@/dojo/setup"; -import { useGetRealm } from "@/hooks/helpers/use-realm"; +import { useDojo } from "@/hooks/context/dojo-context"; import { ResourceIcon } from "@/ui/elements/resource-icon"; import { unpackResources } from "@/ui/utils/packed-data"; +import { getRealmInfo } from "@/utils/realm"; import { ID, ResourcesIds } from "@bibliothecadao/eternum"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; export const RealmResourcesIO = ({ realmEntityId, @@ -10,12 +12,13 @@ export const RealmResourcesIO = ({ titleClassName, size = "xs", }: { - realmEntityId?: ID; + realmEntityId: ID; className?: string; titleClassName?: string; size?: "xs" | "sm" | "md" | "lg" | "xl" | "xxl"; }) => { - const { realm } = useGetRealm(realmEntityId); + const dojo = useDojo(); + const realm = getRealmInfo(getEntityIdFromKeys([BigInt(realmEntityId)]), dojo.setup.components); const resourcesProduced = realm ? unpackResources(realm.resourceTypesPacked) : []; const resourcesInputs = configManager.resourceInputs; diff --git a/client/apps/game/src/ui/components/resources/realm-transfer.tsx b/client/apps/game/src/ui/components/resources/realm-transfer.tsx index 9063f1653..5fa49608f 100644 --- a/client/apps/game/src/ui/components/resources/realm-transfer.tsx +++ b/client/apps/game/src/ui/components/resources/realm-transfer.tsx @@ -1,5 +1,5 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { PlayerStructure, useEntities } from "@/hooks/helpers/use-entities"; +import { usePlayerStructures } from "@/hooks/helpers/use-entities"; import { useResourceManager } from "@/hooks/helpers/use-resources"; import useUIStore from "@/hooks/store/use-ui-store"; import { OSWindow } from "@/ui/components/navigation/os-window"; @@ -7,7 +7,7 @@ import Button from "@/ui/elements/button"; import { NumberInput } from "@/ui/elements/number-input"; import { ResourceIcon } from "@/ui/elements/resource-icon"; import { calculateDonkeysNeeded, currencyFormat, getTotalResourceWeight, multiplyByPrecision } from "@/ui/utils/utils"; -import { ID, ResourcesIds, findResourceById } from "@bibliothecadao/eternum"; +import { ID, PlayerStructure, ResourcesIds, findResourceById } from "@bibliothecadao/eternum"; import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"; import { Dispatch, SetStateAction, memo, useCallback, useEffect, useMemo, useState } from "react"; import { num } from "starknet"; @@ -33,7 +33,7 @@ export const RealmTransfer = memo( const isOpen = useUIStore((state) => state.isPopupOpen(resource.toString())); const selectedStructureEntityId = useUIStore((state) => state.structureEntityId); - const { playerStructures } = useEntities(); + const playerStructures = usePlayerStructures(); const [isLoading, setIsLoading] = useState(false); const [calls, setCalls] = useState([]); @@ -107,7 +107,7 @@ export const RealmTransfer = memo(
{currencyFormat(balance ? Number(balance) : 0, 2)}
- {playerStructures().map((structure) => ( + {playerStructures.map((structure) => ( void; isAmm?: boolean; }) => { + const dojo = useDojo(); + const [resourceWeight, setResourceWeight] = useState(0); const [donkeyBalance, setDonkeyBalance] = useState(0); const neededDonkeys = useMemo(() => calculateDonkeysNeeded(resourceWeight), [resourceWeight]); - const { getBalance } = useResourceBalance(); - useEffect(() => { const totalWeight = getTotalResourceWeight(resources); const multipliedWeight = multiplyByPrecision(totalWeight); setResourceWeight(multipliedWeight); - const { balance } = getBalance(entityId, ResourcesIds.Donkey); + const { balance } = getBalance(entityId, ResourcesIds.Donkey, dojo.setup.components); const currentDonkeyAmount = isAmm ? 0 : resources.find((r) => r.resourceId === ResourcesIds.Donkey)?.amount || 0; diff --git a/client/apps/game/src/ui/components/structures/construction/structure-construction-menu.tsx b/client/apps/game/src/ui/components/structures/construction/structure-construction-menu.tsx index b3a2bc0a4..90dca577c 100644 --- a/client/apps/game/src/ui/components/structures/construction/structure-construction-menu.tsx +++ b/client/apps/game/src/ui/components/structures/construction/structure-construction-menu.tsx @@ -1,10 +1,11 @@ import { configManager } from "@/dojo/setup"; -import { useResourceBalance } from "@/hooks/helpers/use-resources"; +import { useDojo } from "@/hooks/context/dojo-context"; import useUIStore from "@/hooks/store/use-ui-store"; import { StructureCard } from "@/ui/components/structures/construction/structure-card"; import { Headline } from "@/ui/elements/headline"; import { ResourceCost } from "@/ui/elements/resource-cost"; import { multiplyByPrecision } from "@/ui/utils/utils"; +import { getBalance } from "@/utils/resources"; import { HYPERSTRUCTURE_CONSTRUCTION_COSTS_SCALED, HYPERSTRUCTURE_CREATION_COSTS, @@ -25,11 +26,11 @@ export const STRUCTURE_IMAGE_PATHS = { }; export const StructureConstructionMenu = ({ className, entityId }: { className?: string; entityId: number }) => { + const dojo = useDojo(); + const setPreviewBuilding = useUIStore((state) => state.setPreviewBuilding); const previewBuilding = useUIStore((state) => state.previewBuilding); - const { getBalance } = useResourceBalance(); - const buildingTypes = Object.keys(StructureType) .filter((key) => isNaN(Number(key))) .filter( @@ -39,7 +40,7 @@ export const StructureConstructionMenu = ({ className, entityId }: { className?: const checkBalance = (cost: any) => Object.keys(cost).every((resourceId) => { const resourceCost = cost[Number(resourceId)]; - const balance = getBalance(entityId, resourceCost.resource); + const balance = getBalance(entityId, resourceCost.resource, dojo.setup.components); return balance.balance >= multiplyByPrecision(resourceCost.amount); }); @@ -90,6 +91,8 @@ const StructureInfo = ({ entityId: ID | undefined; extraButtons?: React.ReactNode[]; }) => { + const dojo = useDojo(); + // if is hyperstructure, the construction cost are only fragments const isHyperstructure = structureId === StructureType["Hyperstructure"]; const cost = HYPERSTRUCTURE_CREATION_COSTS.filter( @@ -101,8 +104,6 @@ const StructureInfo = ({ ? `+${configManager.getHyperstructureConfig().pointsPerCycle} points` : ""; - const { getBalance } = useResourceBalance(); - return (
{StructureType[structureId]} @@ -117,7 +118,7 @@ const StructureInfo = ({
One time cost
{Object.keys(cost).map((resourceId, index) => { - const balance = getBalance(entityId || 0, ResourcesIds.AncientFragment); + const balance = getBalance(entityId || 0, ResourcesIds.AncientFragment, dojo.setup.components); return ( { const dojo = useDojo(); const [selectedTab, setSelectedTab] = useState(0); - const { playerStructures } = useEntities(); + const playerStructures = usePlayerStructures(); const { toggleModal } = useModalStore(); const bank = useBank(); const { bidOffers, askOffers } = useSetMarket(); @@ -88,8 +88,6 @@ export const MarketModal = () => { const selectedResource = useMarketStore((state) => state.selectedResource); const setSelectedResource = useMarketStore((state) => state.setSelectedResource); - const structures = playerStructures(); - const [isSiegeOngoing, isBattleOngoing] = useMemo(() => { const isSiegeOngoing = battleManager.isSiege(currentBlockTimestamp); const isBattleOngoing = battleManager.isBattleOngoing(currentBlockTimestamp); @@ -217,7 +215,7 @@ export const MarketModal = () => { - {structures.map((structure, index) => ( + {playerStructures.map((structure, index) => ( {structure.name} diff --git a/client/apps/game/src/ui/components/trading/market-order-panel.tsx b/client/apps/game/src/ui/components/trading/market-order-panel.tsx index 2d0d06c4e..e97476b7d 100644 --- a/client/apps/game/src/ui/components/trading/market-order-panel.tsx +++ b/client/apps/game/src/ui/components/trading/market-order-panel.tsx @@ -1,5 +1,4 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useRealm } from "@/hooks/helpers/use-realm"; import { useResourceManager } from "@/hooks/helpers/use-resources"; import { useIsResourcesLocked } from "@/hooks/helpers/use-structures"; import { useTravel } from "@/hooks/helpers/use-travel"; @@ -17,6 +16,7 @@ import { getTotalResourceWeight, multiplyByPrecision, } from "@/ui/utils/utils"; +import { getRealmAddressName } from "@/utils/realm"; import { DONKEY_ENTITY_TYPE, ONE_MONTH, @@ -265,8 +265,6 @@ const OrderRow = memo( [entityId, updateBalance], ); - const { getRealmAddressName } = useRealm(); - const isMakerResourcesLocked = useIsResourcesLocked(offer.makerId); const [confirmOrderModal, setConfirmOrderModal] = useState(false); @@ -359,7 +357,7 @@ const OrderRow = memo( }, [donkeyProductionManager, donkeyProduction, currentDefaultTick]); const accountName = useMemo(() => { - return getRealmAddressName(offer.makerId); + return getRealmAddressName(offer.makerId, dojo.setup.components); }, [offer.originName]); const onAccept = async () => { diff --git a/client/apps/game/src/ui/components/trading/realm-production.tsx b/client/apps/game/src/ui/components/trading/realm-production.tsx index 711a1d298..22f30c914 100644 --- a/client/apps/game/src/ui/components/trading/realm-production.tsx +++ b/client/apps/game/src/ui/components/trading/realm-production.tsx @@ -1,7 +1,7 @@ import { configManager } from "@/dojo/setup"; -import { getRealms } from "@/hooks/helpers/use-realm"; +import { useRealms } from "@/hooks/helpers/use-realm"; import useUIStore from "@/hooks/store/use-ui-store"; -import { RealmResourcesIO } from "@/ui/components/resources/realm-resources-io"; +import { ResourceIcon } from "@/ui/elements/resource-icon"; import { SelectResource } from "@/ui/elements/select-resource"; import { unpackResources } from "@/ui/utils/packed-data"; import { ResourcesIds } from "@bibliothecadao/eternum"; @@ -11,24 +11,35 @@ export const RealmProduction = () => { const setSelectedPlayer = useUIStore((state) => state.setSelectedPlayer); const toggleModal = useUIStore((state) => state.toggleModal); - const realms = getRealms(); + // todo: pay attention to expensive query + const realms = useRealms(); const [filterProduced, setFilterProduced] = useState(null); const [filterConsumed, setFilterConsumed] = useState(null); const resourcesInputs = useMemo(() => configManager.resourceInputs, []); + const realmsProduction = useMemo(() => { + return realms.map((realm) => { + const resourcesProduced = unpackResources(realm.produced_resources); + + return { + ...realm, + resourcesProduced, + }; + }); + }, [realms]); + const filteredRealms = useMemo(() => { if (!realms) return []; - return realms.filter((realm) => { + return realmsProduction.filter((realm) => { if (!realm) return false; - const resourcesProduced = unpackResources(realm.resourceTypesPacked); - if (filterProduced && !resourcesProduced.includes(filterProduced)) return false; + if (filterProduced && !realm.resourcesProduced.includes(filterProduced)) return false; if (filterConsumed) { const resourcesConsumed = new Set( - resourcesProduced.flatMap((resourceId) => + realm.resourcesProduced.flatMap((resourceId) => resourcesInputs[resourceId] .filter((input) => input.resource !== ResourcesIds["Wheat"] && input.resource !== ResourcesIds["Fish"]) .map((input) => input.resource), @@ -40,7 +51,7 @@ export const RealmProduction = () => { return true; }); - }, [realms, filterProduced, filterConsumed, resourcesInputs]); + }, [realmsProduction, filterProduced, filterConsumed, resourcesInputs]); const handleRealmClick = (realm: any) => { toggleModal(null); @@ -67,10 +78,12 @@ export const RealmProduction = () => { className="mb-5 border border-gold/40 rounded-xl p-3 hover:opacity-70" onClick={() => handleRealmClick(realm)} > -

{realm.ownerName}

-

{realm.name}

+
+ {realm.resourcesProduced.map((resourceId) => ( + + ))} +

- {realm.realmId && }
))}
diff --git a/client/apps/game/src/ui/components/trading/resource-arrivals.tsx b/client/apps/game/src/ui/components/trading/resource-arrivals.tsx index a2e2038bb..ed525bae3 100644 --- a/client/apps/game/src/ui/components/trading/resource-arrivals.tsx +++ b/client/apps/game/src/ui/components/trading/resource-arrivals.tsx @@ -1,6 +1,5 @@ import { addToSubscription } from "@/dojo/queries"; import { useDojo } from "@/hooks/context/dojo-context"; -import { ArrivalInfo } from "@/hooks/helpers/use-resource-arrivals"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; import { EntityArrival } from "@/ui/components/entities/entity"; import { HintSection } from "@/ui/components/hints/hint-modal"; @@ -8,6 +7,7 @@ import Button from "@/ui/elements/button"; import { Checkbox } from "@/ui/elements/checkbox"; import { Headline } from "@/ui/elements/headline"; import { HintModalButton } from "@/ui/elements/hint-modal-button"; +import { ArrivalInfo } from "@bibliothecadao/eternum"; import { memo, useEffect, useState } from "react"; import { create } from "zustand"; diff --git a/client/apps/game/src/ui/components/trading/select-entity-from-list.tsx b/client/apps/game/src/ui/components/trading/select-entity-from-list.tsx index a077d1a25..fbed4c45f 100644 --- a/client/apps/game/src/ui/components/trading/select-entity-from-list.tsx +++ b/client/apps/game/src/ui/components/trading/select-entity-from-list.tsx @@ -1,5 +1,6 @@ -import { useRealm } from "@/hooks/helpers/use-realm"; +import { useDojo } from "@/hooks/context/dojo-context"; import Button from "@/ui/elements/button"; +import { getRealmAddressName } from "@/utils/realm"; import { ID } from "@bibliothecadao/eternum"; import clsx from "clsx"; import { memo } from "react"; @@ -18,14 +19,16 @@ interface SelectEntityFromListProps { export const SelectEntityFromList = memo( ({ onSelect, selectedEntityId, selectedCounterpartyId, entities }: SelectEntityFromListProps) => { - const { getRealmAddressName } = useRealm(); + const { + setup: { components }, + } = useDojo(); return (
{entities.map((entity) => { const isSelected = selectedEntityId === entity.entity_id; const isDisabled = isSelected || selectedCounterpartyId === entity.entity_id; - const realmName = getRealmAddressName(entity.entity_id); + const realmName = getRealmAddressName(entity.entity_id, components); return (
{ - const { getBalance } = useResourceBalance(); + const dojo = useDojo(); + const { playResourceSound } = usePlayResourceSound(); const orderedResources = useMemo(() => { @@ -50,12 +52,15 @@ export const SelectResources = ({ return (
{selectedResourceIds.map((id: any, index: any) => { - const resource = getBalance(entity_id, id); + const resource = getBalance(entity_id, id, dojo.setup.components); const options = [orderedResources.find((res) => res.id === id), ...unselectedResources].map((res: any) => ({ id: res.id, label: ( - + ), })); diff --git a/client/apps/game/src/ui/components/trading/trade-history-event.tsx b/client/apps/game/src/ui/components/trading/trade-history-event.tsx index 49578a3b5..26466bf9d 100644 --- a/client/apps/game/src/ui/components/trading/trade-history-event.tsx +++ b/client/apps/game/src/ui/components/trading/trade-history-event.tsx @@ -1,6 +1,7 @@ -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; +import { useDojo } from "@/hooks/context/dojo-context"; import { ResourceIcon } from "@/ui/elements/resource-icon"; import { currencyIntlFormat, divideByPrecision } from "@/ui/utils/utils"; +import { getAddressNameFromEntity } from "@/utils/entities"; import { Resource, ResourcesIds } from "@bibliothecadao/eternum"; import { TradeEvent } from "./market-trading-history"; @@ -24,7 +25,9 @@ export const TradeHistoryRowHeader = () => { }; export const TradeHistoryEvent = ({ trade }: { trade: TradeEvent }) => { - const { getAddressNameFromEntity } = useEntitiesUtils(); + const { + setup: { components }, + } = useDojo(); const resourceTaken = trade.event.resourceTaken; const resourceGiven = trade.event.resourceGiven; @@ -33,7 +36,7 @@ export const TradeHistoryEvent = ({ trade }: { trade: TradeEvent }) => { } const price = getLordsPricePerResource(trade.event.resourceGiven, trade.event.resourceTaken); - const taker = getAddressNameFromEntity(trade.event.takerId); + const taker = getAddressNameFromEntity(trade.event.takerId, components); return (
diff --git a/client/apps/game/src/ui/components/trading/transfer-between-entities.tsx b/client/apps/game/src/ui/components/trading/transfer-between-entities.tsx index d9b84a312..160fe8138 100644 --- a/client/apps/game/src/ui/components/trading/transfer-between-entities.tsx +++ b/client/apps/game/src/ui/components/trading/transfer-between-entities.tsx @@ -1,6 +1,5 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useRealm } from "@/hooks/helpers/use-realm"; import { useTravel } from "@/hooks/helpers/use-travel"; import { soundSelector, useUiSounds } from "@/hooks/use-ui-sound"; import { TravelInfo } from "@/ui/components/resources/travel-info"; @@ -12,6 +11,7 @@ import { Checkbox } from "@/ui/elements/checkbox"; import { Headline } from "@/ui/elements/headline"; import TextInput from "@/ui/elements/text-input"; import { multiplyByPrecision, normalizeDiacriticalMarks } from "@/ui/utils/utils"; +import { getRealmAddressName } from "@/utils/realm"; import { DONKEY_ENTITY_TYPE, ID } from "@bibliothecadao/eternum"; import { ArrowRight, LucideArrowRight } from "lucide-react"; import { memo, useEffect, useMemo, useState } from "react"; @@ -172,8 +172,6 @@ export const TransferBetweenEntities = ({ filtered: boolean; filterBy: (filtered: boolean) => void; }) => { - const { getRealmAddressName } = useRealm(); - const [selectedEntityIdFrom, setSelectedEntityIdFrom] = useState(null); const [selectedEntityIdTo, setSelectedEntityIdTo] = useState(null); const [selectedResourceIds, setSelectedResourceIds] = useState([]); @@ -192,6 +190,7 @@ export const TransferBetweenEntities = ({ const { account: { account }, setup: { + components, systemCalls: { send_resources, pickup_resources }, }, } = useDojo(); @@ -251,7 +250,7 @@ export const TransferBetweenEntities = ({ return entitiesList.map(({ entities, name }) => ({ entities: entities.map((entity) => ({ ...entity, - accountName: getRealmAddressName(entity.entity_id), + accountName: getRealmAddressName(entity.entity_id, components), })), name, })); diff --git a/client/apps/game/src/ui/components/trading/transfer-view.tsx b/client/apps/game/src/ui/components/trading/transfer-view.tsx index c18dc367c..23870ae1e 100644 --- a/client/apps/game/src/ui/components/trading/transfer-view.tsx +++ b/client/apps/game/src/ui/components/trading/transfer-view.tsx @@ -1,9 +1,11 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { PlayerStructure, RealmWithPosition, useEntities, useEntitiesUtils } from "@/hooks/helpers/use-entities"; +import { usePlayerStructures } from "@/hooks/helpers/use-entities"; import { useGuilds } from "@/hooks/helpers/use-guilds"; +import { usePlayerRealms } from "@/hooks/helpers/use-realm"; import { TransferBetweenEntities } from "@/ui/components/trading/transfer-between-entities"; import { getRealmNameById } from "@/ui/utils/realms"; -import { ContractAddress, StructureType } from "@bibliothecadao/eternum"; +import { getEntityName } from "@/utils/entities"; +import { ContractAddress, PlayerStructure, RealmWithPosition, StructureType } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; import { Has, NotValue, getComponentValue } from "@dojoengine/recs"; import { useMemo, useState } from "react"; @@ -11,12 +13,12 @@ import { useMemo, useState } from "react"; export const TransferView = () => { const { account: { account }, - setup: { - components: { Structure, Position, Owner, Realm }, - }, + setup: { components }, } = useDojo(); + const { Structure, Position, Owner, Realm } = components; - const { playerRealms, playerStructures } = useEntities(); + const playerRealms = usePlayerRealms(); + const playerStructures = usePlayerStructures(); const [guildOnly, setGuildOnly] = useState(false); @@ -26,8 +28,6 @@ export const TransferView = () => { return getPlayersInPlayersGuild(BigInt(account.address)).map((a) => BigInt(a.address)); }, [account.address]); - const { getEntityName } = useEntitiesUtils(); - const otherStructuresQuery = useEntityQuery([ Has(Structure), Has(Position), @@ -43,7 +43,7 @@ export const TransferView = () => { const position = getComponentValue(Position, id); - const structureName = getEntityName(structure.entity_id); + const structureName = getEntityName(structure.entity_id, components); const name = structureName ? `${structure?.category} ${structureName}` : structure.category || ""; return { ...structure, position: position!, name, owner: getComponentValue(Owner, id) }; @@ -71,17 +71,17 @@ export const TransferView = () => { filterBy={setGuildOnly} filtered={guildOnly} entitiesList={[ - { entities: playerRealms(), name: "Your Realms" }, + { entities: playerRealms, name: "Your Realms" }, { - entities: playerStructures().filter((structure) => structure.category === "Hyperstructure"), + entities: playerStructures.filter((structure) => structure.category === "Hyperstructure"), name: "Your Hyperstructures", }, { - entities: playerStructures().filter((structure) => structure.category === "FragmentMine"), + entities: playerStructures.filter((structure) => structure.category === "FragmentMine"), name: "Your Fragment Mines", }, { - entities: playerStructures().filter((structure) => structure.category === "Bank"), + entities: playerStructures.filter((structure) => structure.category === "Bank"), name: "Your Banks", }, { diff --git a/client/apps/game/src/ui/components/worldmap/armies/action-info.tsx b/client/apps/game/src/ui/components/worldmap/armies/action-info.tsx index 49d8a20a4..78f1ce200 100644 --- a/client/apps/game/src/ui/components/worldmap/armies/action-info.tsx +++ b/client/apps/game/src/ui/components/worldmap/armies/action-info.tsx @@ -1,13 +1,13 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useResourceBalance } from "@/hooks/helpers/use-resources"; import useUIStore from "@/hooks/store/use-ui-store"; import { BuildingThumbs, FELT_CENTER } from "@/ui/config"; import { BaseThreeTooltip, Position } from "@/ui/elements/base-three-tooltip"; import { Headline } from "@/ui/elements/headline"; import { ResourceCost } from "@/ui/elements/resource-cost"; import { StaminaResourceCost } from "@/ui/elements/stamina-resource-cost"; -import { computeExploreFoodCosts, computeTravelFoodCosts, ResourcesIds } from "@bibliothecadao/eternum"; +import { getBalance } from "@/utils/resources"; +import { computeExploreFoodCosts, computeTravelFoodCosts, ID, ResourcesIds } from "@bibliothecadao/eternum"; import { getComponentValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { memo, useCallback, useMemo } from "react"; @@ -24,9 +24,9 @@ const TooltipContent = memo( isExplored: boolean; travelPath: any; costs: { travelFoodCosts: any; exploreFoodCosts: any }; - selectedEntityId: string; - structureEntityId: string; - getBalance: any; + selectedEntityId: number; + structureEntityId: number; + getBalance: (entityId: ID, resourceId: ResourcesIds) => { balance: number; resourceId: ResourcesIds }; }) => ( <> {isExplored ? "Travel" : "Explore"} @@ -82,11 +82,8 @@ export const ActionInfo = memo(() => { const selectedEntityId = useUIStore(useCallback((state) => state.armyActions.selectedEntityId, [])); const structureEntityId = useUIStore(useCallback((state) => state.structureEntityId, [])); - const { getBalance } = useResourceBalance(); const { - setup: { - components: { Army }, - }, + setup: { components }, } = useDojo(); const selectedEntityTroops = useMemo(() => { @@ -97,7 +94,7 @@ export const ActionInfo = memo(() => { crossbowman_count: 0n, }; } - const army = getComponentValue(Army, getEntityIdFromKeys([BigInt(selectedEntityId)])); + const army = getComponentValue(components.Army, getEntityIdFromKeys([BigInt(selectedEntityId)])); return ( army?.troops || { knight_count: 0n, @@ -105,7 +102,7 @@ export const ActionInfo = memo(() => { crossbowman_count: 0n, } ); - }, [selectedEntityId, Army]); + }, [selectedEntityId]); const travelPath = useMemo(() => { if (!hoveredHex) return undefined; @@ -128,7 +125,7 @@ export const ActionInfo = memo(() => { [selectedEntityTroops], ); - if (!showTooltip) return null; + if (!showTooltip || !selectedEntityId) return null; return ( @@ -136,9 +133,9 @@ export const ActionInfo = memo(() => { isExplored={isExplored} travelPath={travelPath} costs={costs} - selectedEntityId={selectedEntityId!.toString()} - structureEntityId={structureEntityId.toString()} - getBalance={getBalance} + selectedEntityId={selectedEntityId} + structureEntityId={structureEntityId} + getBalance={(entityId: ID, resourceId: ResourcesIds) => getBalance(entityId, resourceId, components)} /> ); diff --git a/client/apps/game/src/ui/components/worldmap/armies/army-info-label.tsx b/client/apps/game/src/ui/components/worldmap/armies/army-info-label.tsx index ecc7b412e..9089be5b3 100644 --- a/client/apps/game/src/ui/components/worldmap/armies/army-info-label.tsx +++ b/client/apps/game/src/ui/components/worldmap/armies/army-info-label.tsx @@ -1,6 +1,6 @@ +import { useDojo } from "@/hooks/context/dojo-context"; import { useGetArmyByEntityId } from "@/hooks/helpers/use-armies"; import { useQuery } from "@/hooks/helpers/use-query"; -import { useRealm } from "@/hooks/helpers/use-realm"; import { useIsStructureImmune, useStructureImmunityTimer, useStructures } from "@/hooks/helpers/use-structures"; import useUIStore from "@/hooks/store/use-ui-store"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; @@ -13,6 +13,7 @@ import { ResourceIcon } from "@/ui/elements/resource-icon"; import { StaminaResource } from "@/ui/elements/stamina-resource"; import { getRealmNameById } from "@/ui/utils/realms"; import { currencyFormat } from "@/ui/utils/utils"; +import { getRealmAddressName } from "@/utils/realm"; import { ArmyInfo, Structure } from "@bibliothecadao/eternum"; import clsx from "clsx"; import { useMemo } from "react"; @@ -35,12 +36,15 @@ interface ArmyInfoLabelProps { } const RaiderInfo = ({ army }: ArmyInfoLabelProps) => { - const { getRealmAddressName } = useRealm(); + const { + setup: { components }, + } = useDojo(); + const { realm, entity_id, entityOwner, troops } = army; const realmId = realm?.realm_id || 0; - const attackerAddressName = entityOwner ? getRealmAddressName(entityOwner.entity_owner_id) : ""; + const attackerAddressName = entityOwner ? getRealmAddressName(entityOwner.entity_owner_id, components) : ""; const { getStructureByEntityId } = useStructures(); diff --git a/client/apps/game/src/ui/components/worldmap/guilds/guild-members.tsx b/client/apps/game/src/ui/components/worldmap/guilds/guild-members.tsx index 33e469b87..d97e84424 100644 --- a/client/apps/game/src/ui/components/worldmap/guilds/guild-members.tsx +++ b/client/apps/game/src/ui/components/worldmap/guilds/guild-members.tsx @@ -7,12 +7,12 @@ import Button from "@/ui/elements/button"; import TextInput from "@/ui/elements/text-input"; import TwitterShareButton from "@/ui/elements/twitter-share-button"; import { formatSocialText, twitterTemplates } from "@/ui/socials"; -import { ContractAddress, ID, Player } from "@bibliothecadao/eternum"; +import { ContractAddress, ID, PlayerInfo } from "@bibliothecadao/eternum"; import { useCallback, useState } from "react"; import { env } from "../../../../../env"; interface GuildMembersProps { - players: Player[]; + players: PlayerInfo[]; selectedGuildEntityId: number; viewPlayerInfo: (playerAddress: ContractAddress) => void; setIsExpanded: (isExpanded: boolean) => void; @@ -23,6 +23,7 @@ interface GuildMembersProps { export const GuildMembers = ({ players, selectedGuildEntityId, viewPlayerInfo, setIsExpanded }: GuildMembersProps) => { const { setup: { + components, systemCalls: { join_guild, remove_guild_member, disband_guild, remove_player_from_whitelist, set_entity_name }, }, account: { account }, @@ -35,7 +36,7 @@ export const GuildMembers = ({ players, selectedGuildEntityId, viewPlayerInfo, s const invitedPlayers = useGuildWhitelist(selectedGuildEntityId, players); const userWhitelist = usePlayerWhitelist(ContractAddress(account.address)); const userGuild = getGuildFromPlayerAddress(ContractAddress(account.address)); - const selectedGuild = getGuildFromEntityId(selectedGuildEntityId, ContractAddress(account.address)); + const selectedGuild = getGuildFromEntityId(selectedGuildEntityId, ContractAddress(account.address), components); const playerName = players.find((player) => player.address === ContractAddress(account?.address))?.name; diff --git a/client/apps/game/src/ui/components/worldmap/guilds/guilds.tsx b/client/apps/game/src/ui/components/worldmap/guilds/guilds.tsx index e22dc2ecd..8c1b0e2d5 100644 --- a/client/apps/game/src/ui/components/worldmap/guilds/guilds.tsx +++ b/client/apps/game/src/ui/components/worldmap/guilds/guilds.tsx @@ -7,7 +7,7 @@ import Button from "@/ui/elements/button"; import { SortInterface } from "@/ui/elements/sort-button"; import TextInput from "@/ui/elements/text-input"; import { sortItems } from "@/ui/utils/utils"; -import { calculateGuildLordsPrize, ContractAddress, ID, Player } from "@bibliothecadao/eternum"; +import { calculateGuildLordsPrize, ContractAddress, ID, PlayerInfo } from "@bibliothecadao/eternum"; import { ChevronRight } from "lucide-react"; import { useMemo, useState } from "react"; @@ -16,7 +16,7 @@ export const Guilds = ({ players, }: { viewGuildMembers: (guildEntityId: ID) => void; - players: Player[]; + players: PlayerInfo[]; }) => { const { setup: { diff --git a/client/apps/game/src/ui/components/worldmap/players/player-list.tsx b/client/apps/game/src/ui/components/worldmap/players/player-list.tsx index 3d14194e7..798e455ed 100644 --- a/client/apps/game/src/ui/components/worldmap/players/player-list.tsx +++ b/client/apps/game/src/ui/components/worldmap/players/player-list.tsx @@ -5,11 +5,11 @@ import { ResourceIcon } from "@/ui/elements/resource-icon"; import { SortButton, SortInterface } from "@/ui/elements/sort-button"; import { SortPanel } from "@/ui/elements/sort-panel"; import { currencyIntlFormat, sortItems } from "@/ui/utils/utils"; -import { ContractAddress, GuildInfo, Player, ResourcesIds } from "@bibliothecadao/eternum"; +import { ContractAddress, GuildInfo, PlayerInfo, ResourcesIds } from "@bibliothecadao/eternum"; import clsx from "clsx"; import { useMemo, useState } from "react"; -export interface PlayerCustom extends Player { +export interface PlayerCustom extends PlayerInfo { structures: string[]; isUser: boolean; isInvited: boolean; diff --git a/client/apps/game/src/ui/components/worldmap/players/players-panel.tsx b/client/apps/game/src/ui/components/worldmap/players/players-panel.tsx index 644b15dbb..687427484 100644 --- a/client/apps/game/src/ui/components/worldmap/players/players-panel.tsx +++ b/client/apps/game/src/ui/components/worldmap/players/players-panel.tsx @@ -1,11 +1,11 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import { useGuilds } from "@/hooks/helpers/use-guilds"; import { PlayerCustom, PlayerList } from "@/ui/components/worldmap/players/player-list"; import Button from "@/ui/elements/button"; import TextInput from "@/ui/elements/text-input"; import { getEntityIdFromKeys, normalizeDiacriticalMarks, toHexString } from "@/ui/utils/utils"; -import { ContractAddress, Player } from "@bibliothecadao/eternum"; +import { getEntityName } from "@/utils/entities"; +import { ContractAddress, PlayerInfo } from "@bibliothecadao/eternum"; import { Has, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; import { KeyboardEvent, useMemo, useState } from "react"; @@ -13,19 +13,20 @@ export const PlayersPanel = ({ players, viewPlayerInfo, }: { - players: Player[]; + players: PlayerInfo[]; viewPlayerInfo: (playerAddress: ContractAddress) => void; }) => { const { setup: { - components: { Structure, Owner, GuildWhitelist }, + components, systemCalls: { whitelist_player, remove_player_from_whitelist }, }, account: { account }, } = useDojo(); + const { Structure, Owner, GuildWhitelist } = components; + const { getGuildFromPlayerAddress } = useGuilds(); - const { getEntityName } = useEntitiesUtils(); const userGuild = getGuildFromPlayerAddress(ContractAddress(account.address)); const [isLoading, setIsLoading] = useState(false); @@ -46,7 +47,7 @@ export const PlayersPanel = ({ const structure = getComponentValue(Structure, entityId); if (!structure) return undefined; - const structureName = getEntityName(structure.entity_id); + const structureName = getEntityName(structure.entity_id, components); return structureName; }) .filter((structure): structure is string => structure !== undefined); diff --git a/client/apps/game/src/ui/layouts/world.tsx b/client/apps/game/src/ui/layouts/world.tsx index a9b9e2186..6bac9f3b7 100644 --- a/client/apps/game/src/ui/layouts/world.tsx +++ b/client/apps/game/src/ui/layouts/world.tsx @@ -5,7 +5,7 @@ import { debouncedAddToSubscriptionOneKey, } from "@/dojo/debounced-queries"; import { useDojo } from "@/hooks/context/dojo-context"; -import { PlayerStructure, useEntities } from "@/hooks/helpers/use-entities"; +import { usePlayerStructures } from "@/hooks/helpers/use-entities"; import { useStructureEntityId } from "@/hooks/helpers/use-structure-entity-id"; import { useFetchBlockchainData } from "@/hooks/store/use-blockchain-store"; import useUIStore from "@/hooks/store/use-ui-store"; @@ -13,7 +13,7 @@ import { LoadingStateKey } from "@/hooks/store/use-world-loading"; import { rewards } from "@/ui/components/navigation/config"; import { LoadingOroborus } from "@/ui/modules/loading-oroborus"; import { LoadingScreen } from "@/ui/modules/loading-screen"; -import { ADMIN_BANK_ENTITY_ID } from "@bibliothecadao/eternum"; +import { ADMIN_BANK_ENTITY_ID, PlayerStructure } from "@bibliothecadao/eternum"; import { getComponentValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { Leva } from "leva"; @@ -114,12 +114,11 @@ export const World = ({ backgroundImage }: { backgroundImage: string }) => { const dojo = useDojo(); const structureEntityId = useUIStore((state) => state.structureEntityId); - const { playerStructures } = useEntities(); - const structures = playerStructures(); + const playerStructures = usePlayerStructures(); const filteredStructures = useMemo( - () => structures.filter((structure: PlayerStructure) => !subscriptions[structure.entity_id.toString()]), - [structures, subscriptions], + () => playerStructures.filter((structure: PlayerStructure) => !subscriptions[structure.entity_id.toString()]), + [playerStructures, subscriptions], ); useEffect(() => { @@ -194,7 +193,7 @@ export const World = ({ backgroundImage }: { backgroundImage: string }) => { await debounceAddDonkeysAndArmiesSubscription( dojo.network.toriiClient, dojo.network.contractComponents as any, - [...structures.map((structure) => structure.entity_id)], + [...playerStructures.map((structure) => structure.entity_id)], () => setLoading(LoadingStateKey.DonkeysAndArmies, false), ); } catch (error) { @@ -203,7 +202,7 @@ export const World = ({ backgroundImage }: { backgroundImage: string }) => { }; fetch(); - }, [structures.length]); + }, [playerStructures.length]); useEffect(() => { const fetch = async () => { @@ -336,7 +335,7 @@ export const World = ({ backgroundImage }: { backgroundImage: string }) => { )} - +
diff --git a/client/apps/game/src/ui/modules/chat/chat.tsx b/client/apps/game/src/ui/modules/chat/chat.tsx index 33147dbaf..3ff43eb63 100644 --- a/client/apps/game/src/ui/modules/chat/chat.tsx +++ b/client/apps/game/src/ui/modules/chat/chat.tsx @@ -1,7 +1,7 @@ import { ReactComponent as Minimize } from "@/assets/icons/common/minimize.svg"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useGetAllPlayers } from "@/hooks/helpers/use-get-all-players"; import { useGuilds } from "@/hooks/helpers/use-guilds"; +import { usePlayers } from "@/hooks/helpers/use-players"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/ui/elements/select"; import TextInput from "@/ui/elements/text-input"; import { ChatTab, DEFAULT_TAB } from "@/ui/modules/chat/chat-tab"; @@ -44,11 +44,7 @@ export const Chat = () => { const addTab = useChatStore((state) => state.addTab); - const getPlayers = useGetAllPlayers(); - - const players = useMemo(() => { - return getPlayers().filter((player) => player.address !== BigInt(account.address)); - }, []); + const players = usePlayers(); useEffect(() => { scrollToElement(bottomChatRef); diff --git a/client/apps/game/src/ui/modules/entity-details/building-entity-details.tsx b/client/apps/game/src/ui/modules/entity-details/building-entity-details.tsx index c04610611..e7f7ca60d 100644 --- a/client/apps/game/src/ui/modules/entity-details/building-entity-details.tsx +++ b/client/apps/game/src/ui/modules/entity-details/building-entity-details.tsx @@ -1,6 +1,6 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntities, useEntitiesUtils } from "@/hooks/helpers/use-entities"; +import { usePlayerStructures } from "@/hooks/helpers/use-entities"; import useUIStore from "@/hooks/store/use-ui-store"; import { soundSelector, useUiSounds } from "@/hooks/use-ui-sound"; import { ResourceMiningTypes } from "@/types"; @@ -9,7 +9,16 @@ import Button from "@/ui/elements/button"; import { RealmDetails } from "@/ui/modules/entity-details/realm/realm-details"; import { LeftView } from "@/ui/modules/navigation/left-navigation-module"; import { ResourceIdToMiningType, getEntityIdFromKeys } from "@/ui/utils/utils"; -import { BUILDINGS_CENTER, BuildingType, ID, ResourcesIds, StructureType, TileManager } from "@bibliothecadao/eternum"; +import { getEntityInfo } from "@/utils/entities"; +import { + BUILDINGS_CENTER, + BuildingType, + ContractAddress, + ID, + ResourcesIds, + StructureType, + TileManager, +} from "@bibliothecadao/eternum"; import { useComponentValue } from "@dojoengine/react"; import { getComponentValue } from "@dojoengine/recs"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -31,8 +40,6 @@ export const BuildingEntityDetails = () => { const [isOwnedByPlayer, setIsOwnedByPlayer] = useState(false); const [showDestroyConfirm, setShowDestroyConfirm] = useState(false); - const { getEntityInfo } = useEntitiesUtils(); - const structureEntityId = useUIStore((state) => state.structureEntityId); const selectedBuildingHex = useUIStore((state) => state.selectedBuildingHex); const setLeftNavigationView = useUIStore((state) => state.setLeftNavigationView); @@ -40,9 +47,13 @@ export const BuildingEntityDetails = () => { const { play: playDestroyStone } = useUiSounds(soundSelector.destroyStone); const { play: playDestroyWooden } = useUiSounds(soundSelector.destroyWooden); - const { playerStructures } = useEntities(); + const playerStructures = usePlayerStructures(); - const selectedStructureInfo = getEntityInfo(structureEntityId); + const selectedStructureInfo = getEntityInfo( + structureEntityId, + ContractAddress(dojo.account.account.address), + dojo.setup.components, + ); const isCastleSelected = useMemo( () => @@ -57,8 +68,6 @@ export const BuildingEntityDetails = () => { getEntityIdFromKeys(Object.values(selectedBuildingHex).map((v) => BigInt(v))), ); - const structures = playerStructures(); - useEffect(() => { if (building) { setBuildingState({ @@ -67,7 +76,7 @@ export const BuildingEntityDetails = () => { ownerEntityId: building.outer_entity_id, }); setIsPaused(building.paused); - setIsOwnedByPlayer(structures.some((structure) => structure.entity_id === building.outer_entity_id)); + setIsOwnedByPlayer(playerStructures.some((structure) => structure.entity_id === building.outer_entity_id)); } else { setBuildingState({ buildingType: undefined, diff --git a/client/apps/game/src/ui/modules/entity-details/enemy-armies.tsx b/client/apps/game/src/ui/modules/entity-details/enemy-armies.tsx index e86d9822b..87bf26d8c 100644 --- a/client/apps/game/src/ui/modules/entity-details/enemy-armies.tsx +++ b/client/apps/game/src/ui/modules/entity-details/enemy-armies.tsx @@ -1,12 +1,12 @@ import { ReactComponent as Swords } from "@/assets/icons/common/cross-swords.svg"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import { useIsStructureImmune, useStructureAtPosition } from "@/hooks/helpers/use-structures"; import useUIStore from "@/hooks/store/use-ui-store"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; import { Position } from "@/types/position"; import { ArmyChip } from "@/ui/components/military/army-chip"; -import { ArmyInfo, BattleManager } from "@bibliothecadao/eternum"; +import { getEntityInfo } from "@/utils/entities"; +import { ArmyInfo, BattleManager, ContractAddress } from "@bibliothecadao/eternum"; import clsx from "clsx"; import React, { useCallback, useMemo } from "react"; @@ -20,7 +20,6 @@ export const EnemyArmies = ({ position: Position; }) => { const dojo = useDojo(); - const { getEntityInfo } = useEntitiesUtils(); const structureAtPosition = useStructureAtPosition(position.getContract()); const { nextBlockTimestamp } = useNextBlockTimestamp(); @@ -28,7 +27,11 @@ export const EnemyArmies = ({ const setBattleView = useUIStore((state) => state.setBattleView); const setTooltip = useUIStore((state) => state.setTooltip); - const entityInfo = getEntityInfo(ownArmySelected?.entityOwner.entity_owner_id ?? 0).structure; + const entityInfo = getEntityInfo( + ownArmySelected?.entityOwner.entity_owner_id ?? 0, + ContractAddress(dojo.account.account.address), + dojo.setup.components, + ).structure; const ownArmystructure = useMemo(() => { return ownArmySelected ? entityInfo : undefined; @@ -42,7 +45,11 @@ export const EnemyArmies = ({ const getArmyChip = useCallback( (army: ArmyInfo, index: number) => { - const structure = getEntityInfo(army.entityOwner.entity_owner_id).structure; + const structure = getEntityInfo( + army.entityOwner.entity_owner_id, + ContractAddress(dojo.account.account.address), + dojo.setup.components, + ).structure; const isImmune = useIsStructureImmune(structure, nextBlockTimestamp!) || ownArmyIsImmune; const button = ownArmySelected && ( diff --git a/client/apps/game/src/ui/modules/entity-details/realm/buildings.tsx b/client/apps/game/src/ui/modules/entity-details/realm/buildings.tsx index 5711ab944..382a8f827 100644 --- a/client/apps/game/src/ui/modules/entity-details/realm/buildings.tsx +++ b/client/apps/game/src/ui/modules/entity-details/realm/buildings.tsx @@ -1,15 +1,15 @@ import { ReactComponent as ArrowRight } from "@/assets/icons/common/arrow-right.svg"; import { useDojo } from "@/hooks/context/dojo-context"; -import { Building, useBuildings } from "@/hooks/helpers/use-buildings"; -import { useGetRealm } from "@/hooks/helpers/use-realm"; +import { useBuildings } from "@/hooks/helpers/use-buildings"; import useUIStore from "@/hooks/store/use-ui-store"; import { BUILDING_IMAGES_PATH } from "@/ui/config"; import Button from "@/ui/elements/button"; import { ResourceIcon } from "@/ui/elements/resource-icon"; -import { toHexString } from "@/ui/utils/utils"; -import { BuildingType, ResourcesIds, TileManager } from "@bibliothecadao/eternum"; +import { getEntityIdFromKeys, toHexString } from "@/ui/utils/utils"; +import { getRealmInfo } from "@/utils/realm"; +import { Building, BuildingType, ResourcesIds, TileManager } from "@bibliothecadao/eternum"; import clsx from "clsx"; -import { useState } from "react"; +import { useMemo, useState } from "react"; export const Buildings = ({ structure }: { structure: any }) => { const dojo = useDojo(); @@ -21,10 +21,12 @@ export const Buildings = ({ structure }: { structure: any }) => { const [showMilitary, setShowMilitary] = useState(false); const [isLoading, setIsLoading] = useState({ isLoading: false, innerCol: 0, innerRow: 0 }); - const realm = useGetRealm(structureEntityId).realm; + const realm = useMemo( + () => getRealmInfo(getEntityIdFromKeys([BigInt(structureEntityId)]), dojo.setup.components), + [structureEntityId, dojo.setup.components], + ); - const { getBuildings } = useBuildings(); - const buildings = getBuildings(realm.position.x, realm.position.y); + const buildings = useBuildings(realm?.position.x || 0, realm?.position.y || 0); const economyBuildings = buildings.filter( (building) => @@ -41,8 +43,6 @@ export const Buildings = ({ structure }: { structure: any }) => { building.category === BuildingType[BuildingType.Stable], ); - const isOwner = toHexString(realm.owner) === dojo.account.account.address; - const handlePauseResumeProduction = (paused: boolean, innerCol: number, innerRow: number) => { setIsLoading({ isLoading: true, innerCol, innerRow }); const tileManager = new TileManager(dojo.setup.components, dojo.network.provider, { @@ -56,6 +56,9 @@ export const Buildings = ({ structure }: { structure: any }) => { }); }; + if (!realm) return null; + const isOwner = toHexString(realm.owner) === dojo.account.account.address; + return (
{/* Economy Section */} diff --git a/client/apps/game/src/ui/modules/entity-details/realm/castle.tsx b/client/apps/game/src/ui/modules/entity-details/realm/castle.tsx index 71ca990ca..e6a74e09c 100644 --- a/client/apps/game/src/ui/modules/entity-details/realm/castle.tsx +++ b/client/apps/game/src/ui/modules/entity-details/realm/castle.tsx @@ -1,14 +1,20 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useGetRealm } from "@/hooks/helpers/use-realm"; -import { useResourceBalance } from "@/hooks/helpers/use-resources"; import { useStructureByEntityId } from "@/hooks/helpers/use-structures"; import useUIStore from "@/hooks/store/use-ui-store"; import { RealmResourcesIO } from "@/ui/components/resources/realm-resources-io"; import Button from "@/ui/elements/button"; import { ResourceCost } from "@/ui/elements/resource-cost"; -import { divideByPrecision, toHexString } from "@/ui/utils/utils"; -import { LEVEL_DESCRIPTIONS, REALM_MAX_LEVEL, RealmLevels, StructureType } from "@bibliothecadao/eternum"; +import { divideByPrecision, getEntityIdFromKeys } from "@/ui/utils/utils"; +import { getRealmInfo } from "@/utils/realm"; +import { getBalance } from "@/utils/resources"; +import { + ContractAddress, + LEVEL_DESCRIPTIONS, + REALM_MAX_LEVEL, + RealmLevels, + StructureType, +} from "@bibliothecadao/eternum"; import { useMemo, useState } from "react"; export const Castle = () => { @@ -16,20 +22,20 @@ export const Castle = () => { const structureEntityId = useUIStore((state) => state.structureEntityId); - const { getBalance } = useResourceBalance(); - const [isLoading, setIsLoading] = useState(false); - const realm = useGetRealm(structureEntityId).realm; - - const isOwner = toHexString(realm.owner) === dojo.account.account.address; + const realmInfo = useMemo( + () => getRealmInfo(getEntityIdFromKeys([BigInt(structureEntityId)]), dojo.setup.components), + [structureEntityId, dojo.setup.components], + ); const structure = useStructureByEntityId(structureEntityId); const getNextRealmLevel = useMemo(() => { - const nextLevel = realm.level + 1; + if (!realmInfo) return null; + const nextLevel = realmInfo.level + 1; return nextLevel < REALM_MAX_LEVEL ? nextLevel : null; - }, [realm.level]); + }, [realmInfo]); const checkBalance = useMemo(() => { if (!getNextRealmLevel) return false; @@ -38,21 +44,25 @@ export const Castle = () => { return Object.keys(cost).every((resourceId) => { const resourceCost = cost[Number(resourceId)]; - const balance = getBalance(structureEntityId, resourceCost.resource); + const balance = getBalance(structureEntityId, resourceCost.resource, dojo.setup.components); return divideByPrecision(balance.balance) >= resourceCost.amount; }); }, [getBalance, structureEntityId]); const levelUpRealm = async () => { setIsLoading(true); + if (!realmInfo) return; await dojo.setup.systemCalls.upgrade_realm({ signer: dojo.account.account, - realm_entity_id: realm.entityId, + realm_entity_id: realmInfo.entityId, }); setIsLoading(false); }; + if (!realmInfo) return null; + const isOwner = realmInfo.owner === ContractAddress(dojo.account.account.address); + return ( structure && (
@@ -60,7 +70,7 @@ export const Castle = () => {
-
{RealmLevels[realm.level]}
+
{RealmLevels[realmInfo.level]}
{getNextRealmLevel && isOwner && ( diff --git a/client/apps/game/src/ui/modules/social/player-id.tsx b/client/apps/game/src/ui/modules/social/player-id.tsx index 7c931db4f..088a0c588 100644 --- a/client/apps/game/src/ui/modules/social/player-id.tsx +++ b/client/apps/game/src/ui/modules/social/player-id.tsx @@ -1,7 +1,6 @@ import { ReactComponent as ArrowLeft } from "@/assets/icons/common/arrow-left.svg"; import { ReactComponent as MessageSvg } from "@/assets/icons/common/message.svg"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import useUIStore from "@/hooks/store/use-ui-store"; import { Position as PositionType } from "@/types/position"; import { NavigateToPositionIcon } from "@/ui/components/military/army-chip"; @@ -9,6 +8,7 @@ import { ViewOnMapIcon } from "@/ui/components/military/army-management-card"; import { RealmResourcesIO } from "@/ui/components/resources/realm-resources-io"; import Button from "@/ui/elements/button"; import { formatTime, toHexString } from "@/ui/utils/utils"; +import { getAddressName, getEntityName } from "@/utils/entities"; import { ContractAddress, StructureType } from "@bibliothecadao/eternum"; import { Has, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; import { useMemo } from "react"; @@ -56,19 +56,15 @@ export const PlayerId = ({ back?: () => void; }) => { const { - setup: { - components: { - Owner, - Structure, - Position, - events: { SettleRealmData }, - }, - }, + setup: { components }, } = useDojo(); - const { getEntityName } = useEntitiesUtils(); - - const { getAddressName } = useEntitiesUtils(); + const { + Owner, + Structure, + Position, + events: { SettleRealmData }, + } = components; const playerEntityId = useMemo(() => { if (!selectedPlayer) return; @@ -83,7 +79,7 @@ export const PlayerId = ({ const playerName = useMemo(() => { if (!selectedPlayer) return; - const playerName = getAddressName(selectedPlayer); + const playerName = getAddressName(selectedPlayer, components); return playerName; }, [selectedPlayer, playerEntityId]); @@ -112,7 +108,7 @@ export const PlayerId = ({ const position = new PositionType({ x: positionComponentValue.x, y: positionComponentValue.y }); - const structureName = getEntityName(structure.entity_id, true); + const structureName = getEntityName(structure.entity_id, components, true); return { structureName, ...structure, diff --git a/client/apps/game/src/ui/modules/social/social.tsx b/client/apps/game/src/ui/modules/social/social.tsx index 1e09e77ae..6c62c6588 100644 --- a/client/apps/game/src/ui/modules/social/social.tsx +++ b/client/apps/game/src/ui/modules/social/social.tsx @@ -1,4 +1,5 @@ import { useDojo } from "@/hooks/context/dojo-context"; +import { usePlayers } from "@/hooks/helpers/use-players"; import { useHyperstructureData, useLeaderBoardStore } from "@/hooks/store/use-leaderboard-store"; import useUIStore from "@/hooks/store/use-ui-store"; import { HintSection } from "@/ui/components/hints/hint-modal"; @@ -9,21 +10,20 @@ import { Guilds } from "@/ui/components/worldmap/guilds/guilds"; import { PlayersPanel } from "@/ui/components/worldmap/players/players-panel"; import Button from "@/ui/elements/button"; import { Tabs } from "@/ui/elements/tab"; -import { ContractAddress, ID, Player } from "@bibliothecadao/eternum"; +import { getPlayerInfo } from "@/utils/players"; +import { ContractAddress, ID, PlayerInfo } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; import { Has } from "@dojoengine/recs"; import { useEffect, useMemo, useState } from "react"; import { EndSeasonButton } from "./end-season-button"; import { PlayerId } from "./player-id"; -export const Social = ({ getPlayers }: { getPlayers: () => Player[] }) => { +export const Social = () => { const { - setup: { - components: { - events: { GameEnded }, - }, - }, + account: { account }, + setup: { components }, } = useDojo(); + const [selectedTab, setSelectedTab] = useState(0); const [isExpanded, setIsExpanded] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -33,11 +33,16 @@ export const Social = ({ getPlayers }: { getPlayers: () => Player[] }) => { const togglePopup = useUIStore((state) => state.togglePopup); const isOpen = useUIStore((state) => state.isPopupOpen(social)); - const gameEnded = useEntityQuery([Has(GameEnded)]); + const gameEnded = useEntityQuery([Has(components.events.GameEnded)]); + + const players = usePlayers(); - const [players, setPlayers] = useState(() => getPlayers()); const playersByRank = useLeaderBoardStore((state) => state.playersByRank); + const [playerInfo, setPlayerInfo] = useState( + getPlayerInfo(players, ContractAddress(account.address), playersByRank, components), + ); + const updateLeaderboard = useHyperstructureData(); const handleUpdatePoints = () => { @@ -46,7 +51,7 @@ export const Social = ({ getPlayers }: { getPlayers: () => Player[] }) => { }; useEffect(() => { - setPlayers(getPlayers()); + setPlayerInfo(getPlayerInfo(players, ContractAddress(account.address), playersByRank, components)); setIsLoading(false); }, [playersByRank]); @@ -74,18 +79,18 @@ export const Social = ({ getPlayers }: { getPlayers: () => Player[] }) => { { key: "Players", label:
Players
, - component: , + component: , expandedContent: , }, { key: "Guild", label:
Tribes
, - component: , + component: , expandedContent: selectedPlayer ? ( viewPlayerInfo(0n)} /> ) : ( Player[] }) => { ), }, ], - [selectedTab, isExpanded, selectedGuild, selectedPlayer, updateLeaderboard], + [selectedTab, isExpanded, selectedGuild, selectedPlayer, playerInfo], ); return ( diff --git a/client/apps/game/src/ui/modules/stream/event-stream.tsx b/client/apps/game/src/ui/modules/stream/event-stream.tsx index 362f08ed9..a7a1fba22 100644 --- a/client/apps/game/src/ui/modules/stream/event-stream.tsx +++ b/client/apps/game/src/ui/modules/stream/event-stream.tsx @@ -1,9 +1,9 @@ import { world } from "@/dojo/world"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import { NavigateToPositionIcon } from "@/ui/components/military/army-chip"; import { ViewOnMapIcon } from "@/ui/components/military/army-management-card"; -import { ContractAddress } from "@bibliothecadao/eternum"; +import { getAddressNameFromEntity, getPlayerAddressFromEntity } from "@/utils/entities"; +import { ContractAddress, ID } from "@bibliothecadao/eternum"; import { Component, defineComponentSystem, Entity, getComponentValue, World } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { useCallback, useEffect, useState } from "react"; @@ -21,7 +21,6 @@ export const EventStream = () => { const [eventList, setEventList] = useState([]); const [activeTab, setActiveTab] = useState<"all" | "personal">("all"); const [hasNewEvents, setHasNewEvents] = useState(false); - const { getAddressNameFromEntity, getPlayerAddressFromEntity } = useEntitiesUtils(); const createEvent = useCallback( (entity: Entity, component: Component, eventType: EventType): EventData | undefined => { @@ -49,14 +48,16 @@ export const EventStream = () => { : getComponentValue(components.Position, getEntityIdFromKeys([BigInt(entityId)])); const name = entityOwner - ? getAddressNameFromEntity(entityOwner?.entity_owner_id) - : getAddressNameFromEntity(entityId); + ? getAddressNameFromEntity(entityOwner?.entity_owner_id, components) + : getAddressNameFromEntity(entityId, components); const owner = entityOwner ? getComponentValue(components.Owner, getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)])) : getComponentValue(components.Owner, getEntityIdFromKeys([BigInt(entityId)])); - const to = eventDetails[eventType].to?.(componentValue! as any, getPlayerAddressFromEntity); + const to = eventDetails[eventType].to?.(componentValue! as any, (id: ID) => + getPlayerAddressFromEntity(id, components), + ); const isPersonal = to === ContractAddress(account.address); return { diff --git a/client/apps/game/src/ui/modules/world-structures/world-structures-menu.tsx b/client/apps/game/src/ui/modules/world-structures/world-structures-menu.tsx index 234aaa2f2..829cb1c78 100644 --- a/client/apps/game/src/ui/modules/world-structures/world-structures-menu.tsx +++ b/client/apps/game/src/ui/modules/world-structures/world-structures-menu.tsx @@ -1,11 +1,8 @@ import { useDojo } from "@/hooks/context/dojo-context"; import { useArmiesAtPosition } from "@/hooks/helpers/use-armies"; -import { useGetHyperstructuresWithContributionsFromPlayer } from "@/hooks/helpers/use-contributions"; -import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import { useFragmentMines } from "@/hooks/helpers/use-fragment-mines"; import { useGuilds } from "@/hooks/helpers/use-guilds"; import { useHyperstructureProgress, useHyperstructures } from "@/hooks/helpers/use-hyperstructures"; -import { useResourceBalance } from "@/hooks/helpers/use-resources"; import { FragmentMinePanel } from "@/ui/components/fragmentMines/fragment-mine-panel"; import { HintSection } from "@/ui/components/hints/hint-modal"; import { DisplayedAccess, HyperstructurePanel } from "@/ui/components/hyperstructures/hyperstructure-panel"; @@ -16,6 +13,8 @@ import { Checkbox } from "@/ui/elements/checkbox"; import { HintModalButton } from "@/ui/elements/hint-modal-button"; import { ResourceIcon } from "@/ui/elements/resource-icon"; import { currencyFormat, currencyIntlFormat, divideByPrecision } from "@/ui/utils/utils"; +import { getAddressNameFromEntity, getPlayerAddressFromEntity } from "@/utils/entities"; +import { getBalance } from "@/utils/resources"; import { BattleSide, ContractAddress, @@ -30,6 +29,7 @@ import { Tabs } from "../../elements/tab"; export const WorldStructuresMenu = ({ className }: { className?: string }) => { const { + setup: { components }, account: { account }, } = useDojo(); @@ -37,8 +37,15 @@ export const WorldStructuresMenu = ({ className }: { className?: string }) => { const [showOnlyMine, setShowOnlyMine] = useState(false); const { hyperstructures } = useHyperstructures(); - const { fragmentMines } = useFragmentMines(); - const myHyperstructures = useGetHyperstructuresWithContributionsFromPlayer(); + const fragmentMines = useFragmentMines(); + + const myHyperstructures = useMemo( + () => + LeaderboardManager.instance(components).getHyperstructuresWithContributionsFromPlayer( + ContractAddress(account.address), + ), + [components, account.address], + ); const renderExtraContent = useCallback( (entityId: ID, type: "hyperstructure" | "fragmentMine") => { @@ -86,7 +93,7 @@ export const WorldStructuresMenu = ({ className }: { className?: string }) => { position: { x: h.x, y: h.y }, ...h, }))} - filterEntityIds={showOnlyMine ? Array.from(myHyperstructures()) : undefined} + filterEntityIds={showOnlyMine ? Array.from(myHyperstructures) : undefined} /> ), @@ -172,14 +179,17 @@ const BaseStructureExtraContent = ({ entityId: ID; children: React.ReactNode; }) => { + const { + setup: { components }, + } = useDojo(); + const { getGuildFromPlayerAddress } = useGuilds(); - const { getAddressNameFromEntity, getPlayerAddressFromEntity } = useEntitiesUtils(); const armies = useArmiesAtPosition({ position: { x, y } }); const structureOwner = useMemo(() => { - const ownerName = getAddressNameFromEntity(entityId); - const address = getPlayerAddressFromEntity(entityId); + const ownerName = getAddressNameFromEntity(entityId, components); + const address = getPlayerAddressFromEntity(entityId, components); const guildName = getGuildFromPlayerAddress(address || 0n)?.name; return { name: ownerName, guildName }; }, [entityId, getAddressNameFromEntity, getPlayerAddressFromEntity, getGuildFromPlayerAddress]); @@ -192,7 +202,7 @@ const BaseStructureExtraContent = ({ const getArmyInfo = (army?: any) => { if (!army) return; - const ownerName = getAddressNameFromEntity(army.entity_id || 0); + const ownerName = getAddressNameFromEntity(army.entity_id || 0, components); const guildName = getGuildFromPlayerAddress(army.owner?.address || 0n)?.name; const totalTroops = (army.troops?.knight_count || 0n) + (army.troops?.paladin_count || 0n) + (army.troops?.crossbowman_count || 0n); @@ -265,8 +275,9 @@ const HyperStructureExtraContent = ({ }; const FragmentMineExtraContent = ({ x, y, entityId }: { x: number; y: number; entityId: ID }) => { - const { getBalance } = useResourceBalance(); - const { balance } = getBalance(entityId, ResourcesIds.AncientFragment); + const dojo = useDojo(); + + const { balance } = getBalance(entityId, ResourcesIds.AncientFragment, dojo.setup.components); const trait = useMemo(() => findResourceById(ResourcesIds.AncientFragment)?.trait, []); return ( diff --git a/client/apps/game/src/ui/utils/realms.tsx b/client/apps/game/src/ui/utils/realms.tsx index f02285c67..df793292d 100644 --- a/client/apps/game/src/ui/utils/realms.tsx +++ b/client/apps/game/src/ui/utils/realms.tsx @@ -1,6 +1,5 @@ import { configManager } from "@/dojo/setup"; -import { ClientComponents, findResourceIdByTrait, ID, orders, RealmInterface } from "@bibliothecadao/eternum"; -import { ComponentValue } from "@dojoengine/recs"; +import { findResourceIdByTrait, ID, orders, RealmInterface } from "@bibliothecadao/eternum"; import realmsJson from "../../../../../common/data/realms.json"; import { packResources } from "./packed-data"; @@ -76,8 +75,3 @@ export const hasEnoughPopulationForBuilding = (realm: any, building: number) => return (realm?.population || 0) + buildingPopulation <= basePopulationCapacity + (realm?.capacity || 0); }; - -export const getRealmName = (realm: ComponentValue) => { - const baseName = getRealmNameById(realm.realm_id); - return realm.has_wonder ? `WONDER - ${baseName}` : baseName; -}; diff --git a/client/apps/game/src/utils/army.ts b/client/apps/game/src/utils/army.ts index fc6c9443f..a7962e3bc 100644 --- a/client/apps/game/src/utils/army.ts +++ b/client/apps/game/src/utils/army.ts @@ -104,3 +104,19 @@ export const formatArmies = (armies: Entity[], playerAddress: string, components }) .filter((army): army is ArmyInfo => army !== undefined); }; + +export const armyHasTroops = (entityArmies: (ArmyInfo | undefined)[]) => { + return entityArmies.some( + (army) => + army && + (Number(army.troops.knight_count) !== 0 || + Number(army.troops.crossbowman_count) !== 0 || + Number(army.troops.paladin_count) !== 0), + ); +}; + +export const armyHasTraveled = (entityArmies: ArmyInfo[], realmPosition: { x: number; y: number }) => { + return entityArmies.some( + (army) => army && realmPosition && (army.position.x !== realmPosition.x || army.position.y !== realmPosition.y), + ); +}; diff --git a/client/apps/game/src/utils/entities.ts b/client/apps/game/src/utils/entities.ts new file mode 100644 index 000000000..7752ce4bf --- /dev/null +++ b/client/apps/game/src/utils/entities.ts @@ -0,0 +1,157 @@ +import { getRealmNameById } from "@/ui/utils/realms"; +import { + CAPACITY_CONFIG_CATEGORY_STRING_MAP, + ClientComponents, + ContractAddress, + divideByPrecision, + EntityType, + ID, + PlayerStructure, + RealmWithPosition, +} from "@bibliothecadao/eternum"; +import { ComponentValue, getComponentValue } from "@dojoengine/recs"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import { getResourcesFromBalance } from "./resources"; + +export const getRealm = (entityId: ID, components: ClientComponents) => { + const { Realm, Owner, Position } = components; + const entity = getEntityIdFromKeys([BigInt(entityId)]); + const realm = getComponentValue(Realm, entity); + if (!realm) return undefined; + + const position = getComponentValue(Position, entity); + const owner = getComponentValue(Owner, entity); + + return { + ...realm, + position, + name: getRealmNameById(realm.realm_id), + owner, + } as RealmWithPosition; +}; + +export const getStructure = (entityId: ID, components: ClientComponents) => { + const { Structure, Position, Owner } = components; + const entity = getEntityIdFromKeys([BigInt(entityId)]); + const structure = getComponentValue(Structure, entity); + if (!structure) return undefined; + + const position = getComponentValue(Position, entity); + const owner = getComponentValue(Owner, entity); + const structureName = getEntityName(structure.entity_id, components); + const name = structureName ? `${structure?.category} ${structureName}` : structure.category || ""; + + return { + ...structure, + position, + name, + owner, + } as PlayerStructure; +}; + +export const getEntityInfo = (entityId: ID, playerAccount: ContractAddress, components: ClientComponents) => { + const { ArrivalTime, Movable, CapacityCategory, CapacityConfig, EntityOwner, Owner, Structure, Army, Position } = + components; + const entityIdBigInt = BigInt(entityId); + const arrivalTime = getComponentValue(ArrivalTime, getEntityIdFromKeys([entityIdBigInt])); + const movable = getComponentValue(Movable, getEntityIdFromKeys([entityIdBigInt])); + + const entityCapacityCategory = getComponentValue(CapacityCategory, getEntityIdFromKeys([entityIdBigInt])) + ?.category as unknown as string; + const capacityCategoryId = CAPACITY_CONFIG_CATEGORY_STRING_MAP[entityCapacityCategory] || 0n; + const capacity = getComponentValue(CapacityConfig, getEntityIdFromKeys([BigInt(capacityCategoryId)])); + + const entityOwner = getComponentValue(EntityOwner, getEntityIdFromKeys([entityIdBigInt])); + const owner = getComponentValue(Owner, getEntityIdFromKeys([BigInt(entityOwner?.entity_owner_id || 0)])); + + const name = getEntityName(entityId, components); + + const structure = getComponentValue(Structure, getEntityIdFromKeys([entityIdBigInt])); + + const resources = getResourcesFromBalance(entityId, components); + const army = getComponentValue(Army, getEntityIdFromKeys([entityIdBigInt])); + const rawIntermediateDestination = movable + ? { x: movable.intermediate_coord_x, y: movable.intermediate_coord_y } + : undefined; + const intermediateDestination = rawIntermediateDestination + ? { x: rawIntermediateDestination.x, y: rawIntermediateDestination.y } + : undefined; + + const position = getComponentValue(Position, getEntityIdFromKeys([entityIdBigInt])); + + const homePosition = entityOwner + ? getComponentValue(Position, getEntityIdFromKeys([BigInt(entityOwner?.entity_owner_id || 0)])) + : undefined; + + return { + entityId, + arrivalTime: arrivalTime?.arrives_at, + blocked: Boolean(movable?.blocked), + capacity: divideByPrecision(Number(capacity?.weight_gram) || 0), + intermediateDestination, + position: position ? { x: position.x, y: position.y } : undefined, + homePosition: homePosition ? { x: homePosition.x, y: homePosition.y } : undefined, + owner: owner?.address, + isMine: ContractAddress(owner?.address || 0n) === playerAccount, + isRoundTrip: movable?.round_trip || false, + resources, + entityType: army ? EntityType.TROOP : EntityType.DONKEY, + structureCategory: structure?.category, + structure, + name, + }; +}; + +export const getEntityName = (entityId: ID, components: ClientComponents, abbreviate: boolean = false) => { + const entityName = getComponentValue(components.EntityName, getEntityIdFromKeys([BigInt(entityId)])); + const realm = getComponentValue(components.Realm, getEntityIdFromKeys([BigInt(entityId)])); + const structure = getComponentValue(components.Structure, getEntityIdFromKeys([BigInt(entityId)])); + if (structure?.category === "Realm" && realm) { + return getRealmName(realm); + } + + if (entityName) { + return entityName.name.toString(); + } + + if (abbreviate && structure) { + const abbreviations: Record = { + FragmentMine: "FM", + Hyperstructure: "HS", + Bank: "BK", + }; + + const abbr = abbreviations[structure.category]; + if (abbr) { + return `${abbr} ${structure.entity_id}`; + } + } + return `${structure?.category} ${structure?.entity_id}`; +}; + +const getRealmName = (realm: ComponentValue) => { + const baseName = getRealmNameById(realm.realm_id); + return realm.has_wonder ? `WONDER - ${baseName}` : baseName; +}; + +export const getAddressName = (address: ContractAddress, components: ClientComponents) => { + const addressName = getComponentValue(components.AddressName, getEntityIdFromKeys([BigInt(address)])); + + return addressName ? addressName.name.toString() : undefined; +}; + +export const getAddressNameFromEntity = (entityId: ID, components: ClientComponents) => { + const address = getPlayerAddressFromEntity(entityId, components); + if (!address) return; + + const addressName = getComponentValue(components.AddressName, getEntityIdFromKeys([BigInt(address)])); + + return addressName ? addressName.name.toString() : undefined; +}; + +export const getPlayerAddressFromEntity = (entityId: ID, components: ClientComponents): ContractAddress | undefined => { + const entityOwner = getComponentValue(components.EntityOwner, getEntityIdFromKeys([BigInt(entityId)])); + return entityOwner?.entity_owner_id + ? getComponentValue(components.Owner, getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)]))?.address + : undefined; +}; diff --git a/client/apps/game/src/utils/players.ts b/client/apps/game/src/utils/players.ts new file mode 100644 index 000000000..f2ea8abe7 --- /dev/null +++ b/client/apps/game/src/utils/players.ts @@ -0,0 +1,60 @@ +import { calculatePlayerSharePercentage } from "@/ui/utils/leaderboard"; +import { ClientComponents, ContractAddress, Player, PlayerInfo, StructureType } from "@bibliothecadao/eternum"; +import { getComponentValue, Has, HasValue, runQuery } from "@dojoengine/recs"; +import { getEntityName } from "./entities"; + +export const getPlayerInfo = ( + players: Player[], + playerAddress: ContractAddress, + playersByRank: [bigint, number][], + components: ClientComponents, +): PlayerInfo[] => { + const { Realm, Owner, GuildMember, Hyperstructure, Structure } = components; + + const totalPoints = playersByRank.reduce((sum, [, points]) => sum + points, 0); + + const playerInfo = players + .map((player) => { + const isAlive = !!runQuery([HasValue(Owner, { address: player.address })]).size; + + const guildMember = getComponentValue(GuildMember, player.entity); + const guildName = guildMember ? getEntityName(guildMember.guild_entity_id, components) : ""; + + return { + entity: player.entity, + address: player.address, + name: player.name, + isAlive, + guildName, + }; + }) + .filter((player) => player !== undefined); + + let unrankedCount = 0; + + return playerInfo.map((player) => { + const rankIndex = playersByRank.findIndex(([address]) => address === player.address); + if (rankIndex === -1) unrankedCount++; + + const points = rankIndex === -1 ? 0 : playersByRank[rankIndex][1]; + + return { + entity: player.entity, + name: player.name, + address: player.address, + points, + rank: rankIndex === -1 ? Number.MAX_SAFE_INTEGER : rankIndex + 1, + percentage: calculatePlayerSharePercentage(points, totalPoints), + lords: 0, + realms: runQuery([Has(Realm), HasValue(Owner, { address: player.address })]).size, + mines: runQuery([ + HasValue(Structure, { category: StructureType[StructureType.FragmentMine] }), + HasValue(Owner, { address: player.address }), + ]).size, + hyperstructures: runQuery([Has(Hyperstructure), HasValue(Owner, { address: player.address })]).size, + isAlive: player.isAlive, + guildName: player.guildName || "", + isUser: player.address === playerAddress, + }; + }); +}; diff --git a/client/apps/game/src/utils/realm.ts b/client/apps/game/src/utils/realm.ts new file mode 100644 index 000000000..393efe5db --- /dev/null +++ b/client/apps/game/src/utils/realm.ts @@ -0,0 +1,72 @@ +import { configManager } from "@/dojo/setup"; +import { ClientComponents, ID, RealmInfo } from "@bibliothecadao/eternum"; +import { Entity, getComponentValue } from "@dojoengine/recs"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import { shortString } from "starknet"; +import realmsJson from "../../../../common/data/realms.json"; + +export const getRealmAddressName = (realmEntityId: ID, components: ClientComponents) => { + const owner = getComponentValue(components.Owner, getEntityIdFromKeys([BigInt(realmEntityId)])); + const addressName = owner + ? getComponentValue(components.AddressName, getEntityIdFromKeys([owner.address])) + : undefined; + + if (addressName) { + return shortString.decodeShortString(String(addressName.name)); + } else { + return ""; + } +}; + +interface Attribute { + trait_type: string; + value: any; +} + +let realms: { + [key: string]: any; +} = {}; + +const loadRealms = async () => { + const response = await fetch("/jsons/realms.json"); + realms = await response.json(); +}; + +loadRealms(); + +const getRealmNameById = (realmId: ID): string => { + const features = realmsJson["features"][realmId - 1]; + if (!features) return ""; + return features["name"]; +}; + +export function getRealmInfo(entity: Entity, components: ClientComponents): RealmInfo | undefined { + const realm = getComponentValue(components.Realm, entity); + const owner = getComponentValue(components.Owner, entity); + const position = getComponentValue(components.Position, entity); + const population = getComponentValue(components.Population, entity); + + if (realm && owner && position) { + const { realm_id, entity_id, produced_resources, order, level } = realm; + + const name = getRealmNameById(realm_id); + + const { address } = owner; + + return { + realmId: realm_id, + entityId: entity_id, + name, + level, + resourceTypesPacked: produced_resources, + order, + position, + ...population, + hasCapacity: + !population || population.capacity + configManager.getBasePopulationCapacity() > population.population, + owner: address, + ownerName: getRealmAddressName(realm.entity_id, components), + hasWonder: realm.has_wonder, + }; + } +} diff --git a/client/apps/game/src/utils/resources.ts b/client/apps/game/src/utils/resources.ts new file mode 100644 index 000000000..e330e1a71 --- /dev/null +++ b/client/apps/game/src/utils/resources.ts @@ -0,0 +1,67 @@ +import useUIStore from "@/hooks/store/use-ui-store"; +import { unpackResources } from "@/ui/utils/packed-data"; +import { + ClientComponents, + configManager, + getStartingResources, + ID, + Resource, + ResourceManager, + resources, + ResourcesIds, +} from "@bibliothecadao/eternum"; +import { getComponentValue } from "@dojoengine/recs"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; + +// used for entities that don't have any production +export const getInventoryResources = (entityId: ID, components: ClientComponents): Resource[] => { + return resources + .map(({ id }) => { + const resource = getComponentValue(components.Resource, getEntityIdFromKeys([BigInt(entityId), BigInt(id)])); + if (resource?.balance !== undefined && resource.balance > 0n) { + return { resourceId: id, amount: Number(resource.balance) }; + } + return undefined; + }) + .filter((resource): resource is Resource => resource !== undefined); +}; + +// for entities that have production like realms +export const getBalance = (entityId: ID, resourceId: ResourcesIds, components: ClientComponents) => { + const currentDefaultTick = useUIStore.getState().currentDefaultTick; + const resourceManager = new ResourceManager(components, entityId, resourceId); + return { balance: resourceManager.balance(currentDefaultTick), resourceId }; +}; + +export const getResourceProductionInfo = (entityId: ID, resourceId: ResourcesIds, components: ClientComponents) => { + const resourceManager = new ResourceManager(components, entityId, resourceId); + return resourceManager.getProduction(); +}; + +export const getResourcesFromBalance = (entityId: ID, components: ClientComponents): Resource[] => { + const currentDefaultTick = useUIStore.getState().currentDefaultTick; + + const weight = getComponentValue(components.Weight, getEntityIdFromKeys([BigInt(entityId)])); + const hasWeightlessResources = configManager + .getWeightLessResources() + .some( + (resourceId) => + (getComponentValue(components.Resource, getEntityIdFromKeys([BigInt(entityId), BigInt(resourceId)]))?.balance ?? + 0n) > 0n, + ); + if (!weight?.value && !hasWeightlessResources) return []; + const resourceIds = resources.map((r) => r.id); + return resourceIds + .map((id) => { + const resourceManager = new ResourceManager(components, entityId, id); + const balance = resourceManager.balance(currentDefaultTick); + return { resourceId: id, amount: balance }; + }) + .filter((r) => r.amount > 0); +}; + +export const getQuestResources = (realmEntityId: ID, components: ClientComponents) => { + const realm = getComponentValue(components.Realm, getEntityIdFromKeys([BigInt(realmEntityId)])); + const resourcesProduced = realm ? unpackResources(realm.produced_resources) : []; + return getStartingResources(resourcesProduced); +}; diff --git a/client/sdk/packages/eternum/src/constants/resources.ts b/client/sdk/packages/eternum/src/constants/resources.ts index 3d1984158..a66f9b4b5 100644 --- a/client/sdk/packages/eternum/src/constants/resources.ts +++ b/client/sdk/packages/eternum/src/constants/resources.ts @@ -1,6 +1,11 @@ import { ResourceInputs, ResourceOutputs, Resources } from "../types"; import { ResourcesIds } from "./index"; +export const DONKEY_RESOURCE_TRACKER = 452312848583266388373324160190187140051835877600158453279131187530910662656n; +export const LORDS_RESOURCE_TRACKER = 7237005577332262213973186563042994240829374041602535252466099000494570602496n; +export const LORDS_AND_DONKEY_RESOURCE_TRACKER = + 7689318425915528602346510723233181380881209919202693705745230188025481265152n; + export const resources: Array = [ { trait: "Stone", diff --git a/client/sdk/packages/eternum/src/modelManager/LeaderboardManager.ts b/client/sdk/packages/eternum/src/modelManager/LeaderboardManager.ts index 2feb3dda0..feb879ef4 100644 --- a/client/sdk/packages/eternum/src/modelManager/LeaderboardManager.ts +++ b/client/sdk/packages/eternum/src/modelManager/LeaderboardManager.ts @@ -1,8 +1,9 @@ -import { Entity, getComponentValue, HasValue, runQuery } from "@dojoengine/recs"; +import { ComponentValue, Entity, getComponentValue, HasValue, runQuery } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { RESOURCE_RARITY, ResourcesIds, WORLD_CONFIG_ID } from "../constants"; import { ClientComponents } from "../dojo/createClientComponents"; -import { ContractAddress, GuildInfo, ID, TickIds } from "../types"; +import { ContractAddress, GuildInfo, ID, Resource, TickIds } from "../types"; +import { divideByPrecision } from "../utils"; import { configManager } from "./ConfigManager"; export class LeaderboardManager { @@ -177,4 +178,49 @@ export class LeaderboardManager { return 0; } + + public getContributions = (hyperstructureEntityId: ID) => { + const contributionsToHyperstructure = Array.from( + runQuery([HasValue(this.components.Contribution, { hyperstructure_entity_id: hyperstructureEntityId })]), + ).map((id) => getComponentValue(this.components.Contribution, id)); + + return contributionsToHyperstructure as ComponentValue[]; + }; + + public getHyperstructuresWithContributionsFromPlayer = (address: ContractAddress) => { + const entityIds = runQuery([HasValue(this.components.Contribution, { player_address: address })]); + const hyperstructureEntityIds = Array.from(entityIds).map( + (entityId) => getComponentValue(this.components.Contribution, entityId)?.hyperstructure_entity_id ?? 0, + ); + return new Set(hyperstructureEntityIds); + }; + + public getPlayerUnregistredContributions = (address: ContractAddress) => { + const registeredContributionsEntities = runQuery([ + HasValue(this.components.LeaderboardRegisterContribution, { address: address }), + ]); + const registeredContributions = Array.from(registeredContributionsEntities) + .map( + (entityId) => + getComponentValue(this.components.LeaderboardRegisterContribution, entityId)?.hyperstructure_entity_id, + ) + .filter((x): x is number => x !== undefined); + const hyperstructuresContributedTo = Array.from(this.getHyperstructuresWithContributionsFromPlayer(address)); + return hyperstructuresContributedTo.filter( + (hyperstructureEntityId) => + !registeredContributions.some((contribution) => contribution === hyperstructureEntityId), + ); + }; + + public getContributionsTotalPercentage = (hyperstructureId: number, contributions: Resource[]) => { + const totalPlayerContribution = divideByPrecision( + contributions.reduce((acc, { amount, resourceId }) => { + return acc + amount * configManager.getResourceRarity(resourceId); + }, 0), + ); + + const totalHyperstructureContribution = configManager.getHyperstructureTotalContributableAmount(hyperstructureId); + + return totalPlayerContribution / totalHyperstructureContribution; + }; } diff --git a/client/sdk/packages/eternum/src/types/common.ts b/client/sdk/packages/eternum/src/types/common.ts index 19711d842..359965970 100644 --- a/client/sdk/packages/eternum/src/types/common.ts +++ b/client/sdk/packages/eternum/src/types/common.ts @@ -1,3 +1,4 @@ +import { ComponentValue, Entity } from "@dojoengine/recs"; import { BuildingType, CapacityConfigCategory, @@ -7,6 +8,51 @@ import { ResourceTier, TroopFoodConsumption, } from "../constants"; +import { ClientComponents } from "../dojo"; + +export type ArrivalInfo = { + entityId: ID; + recipientEntityId: ID; + position: Position; + arrivesAt: bigint; + isOwner: boolean; + hasResources: boolean; + isHome: boolean; +}; + +export type PlayerStructure = ComponentValue & { + position: ComponentValue; + name: string; + category?: string | undefined; + owner: ComponentValue; +}; + +export type RealmWithPosition = ComponentValue & { + position: ComponentValue; + name: string; + owner: ComponentValue; +}; +export interface Prize { + id: QuestType; + title: string; +} + +export enum QuestStatus { + InProgress, + Completed, + Claimed, +} + +export interface Building { + name: string; + category: string; + paused: boolean; + produced: ResourceCost; + consumed: ResourceCost[]; + bonusPercent: number; + innerCol: number; + innerRow: number; +} export enum BattleType { Hex, @@ -457,7 +503,24 @@ export interface Config { realmMaxLevel: number; } -export interface Player { +export interface RealmInfo { + realmId: ID; + entityId: ID; + name: string; + resourceTypesPacked: bigint; + order: number; + position: ComponentValue; + population?: number | undefined; + capacity?: number; + hasCapacity: boolean; + owner: ContractAddress; + ownerName: string; + hasWonder: boolean; + level: number; +} + +export interface PlayerInfo { + entity: Entity; rank: number; address: bigint; name: string; @@ -471,6 +534,12 @@ export interface Player { guildName: string; } +export interface Player { + entity: Entity; + address: ContractAddress; + name: string; +} + export type GuildInfo = { entityId: ID; name: string; diff --git a/client/sdk/packages/eternum/src/utils/index.ts b/client/sdk/packages/eternum/src/utils/index.ts index 4ee67f35c..b9f0939ea 100644 --- a/client/sdk/packages/eternum/src/utils/index.ts +++ b/client/sdk/packages/eternum/src/utils/index.ts @@ -62,7 +62,7 @@ export const applyInputProductionFactor = ( return questResources; }; -export const getQuestResources = (resourcesOnRealm: number[]): ResourceInputs => { +export const getStartingResources = (resourcesOnRealm: number[]): ResourceInputs => { let QUEST_RESOURCES_SCALED: ResourceInputs = scaleResourceInputs( QUEST_RESOURCES, EternumGlobalConfig.resources.resourceMultiplier,