diff --git a/client/.env.preview b/client/.env.preview index 0054369a3..9c1260517 100644 --- a/client/.env.preview +++ b/client/.env.preview @@ -3,13 +3,13 @@ VITE_PUBLIC_MASTER_PRIVATE_KEY=0x075362a844768f31c8058ce31aec3dd7751686440b4f220 VITE_PUBLIC_WORLD_ADDRESS="0x00fd85ef42eaed3b90d02d2cdc7417d6cae189ff4ba876aa5608551afbf1fb47" VITE_PUBLIC_ACCOUNT_CLASS_HASH="0x07dc7899aa655b0aae51eadff6d801a58e97dd99cf4666ee59e704249e51adf2" VITE_PUBLIC_FEE_TOKEN_ADDRESS=0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 -VITE_PUBLIC_TORII=https://api.cartridge.gg/x/sepolia-rc-17/torii +VITE_PUBLIC_TORII=https://api.cartridge.gg/x/sepolia-rc-18/torii VITE_PUBLIC_NODE_URL=https://api.cartridge.gg/x/starknet/sepolia VITE_PUBLIC_DEV=false VITE_PUBLIC_GAME_VERSION="v1.0.0-rc7" VITE_PUBLIC_SHOW_FPS=false VITE_PUBLIC_GRAPHICS_DEV=false -VITE_PUBLIC_TORII_RELAY=/dns4/api.cartridge.gg/tcp/443/x-parity-wss/%2Fx%2Fsepolia-rc-17%2Ftorii%2Fwss +VITE_PUBLIC_TORII_RELAY=/dns4/api.cartridge.gg/tcp/443/x-parity-wss/%2Fx%2Fsepolia-rc-18%2Ftorii%2Fwss VITE_SEASON_PASS_ADDRESS=0x23cc88996a5f9c7bcb559fdcffc257c0f75abe60f2a7e5d5cd343f8a95967f7 VITE_REALMS_ADDRESS=0x3205f47bd6f0b5e9cd5c79fcae19e12523a024709776d0a9e8b375adf63468d VITE_LORDS_ADDRESS=0x0342ad5cc14002c005a5cedcfce2bd3af98d5e7fb79e9bf949b3a91cf145d72e diff --git a/client/src/dojo/modelManager/ConfigManager.ts b/client/src/dojo/modelManager/ConfigManager.ts index 99e234917..466c151fd 100644 --- a/client/src/dojo/modelManager/ConfigManager.ts +++ b/client/src/dojo/modelManager/ConfigManager.ts @@ -1,4 +1,3 @@ -import { divideByPrecision } from "@/ui/utils/utils"; import { ADMIN_BANK_ENTITY_ID, BUILDING_CATEGORY_POPULATION_CONFIG_ID, @@ -78,7 +77,7 @@ export class ClientConfigManager { // if (productionInput) { // const resource = productionInput.input_resource_type; - // const amount = divideByPrecision(Number(productionInput.input_resource_amount)); + // const amount = this.divideByPrecision(Number(productionInput.input_resource_amount)); // inputs.push({ resource, amount }); // } // } @@ -108,7 +107,7 @@ export class ClientConfigManager { this.resourceOutput[Number(resourceType)] = { resource: Number(resourceType) as ResourcesIds, - amount: divideByPrecision(Number(productionConfig?.amount)), + amount: this.divideByPrecision(Number(productionConfig?.amount)), }; } } @@ -156,7 +155,7 @@ export class ClientConfigManager { // ); // if (resource) { // const resourceId = resource.resource_type; - // const amount = divideByPrecision(Number(resource.resource_amount)); + // const amount = this.divideByPrecision(Number(resource.resource_amount)); // resources.push({ resource: resourceId, amount }); // } // } @@ -254,7 +253,7 @@ export class ClientConfigManager { ); return { - amount: divideByPrecision(Number(hyperstructureResourceConfig?.min_amount) ?? 0), + amount: this.divideByPrecision(Number(hyperstructureResourceConfig?.min_amount) ?? 0), resource: ResourcesIds.AncientFragment, }; } @@ -293,7 +292,7 @@ export class ClientConfigManager { return this.getValueOrDefault(() => { const exploreConfig = getComponentValue(this.components.MapConfig, getEntityIdFromKeys([WORLD_CONFIG_ID])); - return divideByPrecision(Number(exploreConfig?.reward_resource_amount ?? 0)); + return this.divideByPrecision(Number(exploreConfig?.reward_resource_amount ?? 0)); }, 0); } @@ -309,7 +308,7 @@ export class ClientConfigManager { crossbowmanStrength: troopConfig?.crossbowman_strength ?? 0, advantagePercent: troopConfig?.advantage_percent ?? 0, disadvantagePercent: troopConfig?.disadvantage_percent ?? 0, - maxTroopCount: divideByPrecision(troopConfig?.max_troop_count ?? 0), + maxTroopCount: this.divideByPrecision(troopConfig?.max_troop_count ?? 0), pillageHealthDivisor: troopConfig?.pillage_health_divisor ?? 0, baseArmyNumberForStructure: troopConfig?.army_free_per_structure ?? 0, armyExtraPerMilitaryBuilding: troopConfig?.army_extra_per_building ?? 0, @@ -414,7 +413,7 @@ export class ClientConfigManager { const bankConfig = getComponentValue(this.components.BankConfig, getEntityIdFromKeys([WORLD_CONFIG_ID])); return { - lordsCost: divideByPrecision(Number(bankConfig?.lords_cost)), + lordsCost: this.divideByPrecision(Number(bankConfig?.lords_cost)), lpFeesNumerator: Number(bankConfig?.lp_fee_num ?? 0), lpFeesDenominator: Number(bankConfig?.lp_fee_denom ?? 0), }; @@ -557,11 +556,11 @@ export class ClientConfigManager { const maxAmount = Number(hyperstructureResourceConfig.max_amount); if (minAmount === maxAmount) { - return divideByPrecision(minAmount); + return this.divideByPrecision(minAmount); } const additionalAmount = Number(randomness % BigInt(maxAmount - minAmount)); - return divideByPrecision(minAmount + Number(additionalAmount)); + return this.divideByPrecision(minAmount + Number(additionalAmount)); } getBasePopulationCapacity(): number { @@ -626,6 +625,10 @@ export class ClientConfigManager { return EternumGlobalConfig.resources.resourcePrecision; } + divideByPrecision(value: number) { + return value / EternumGlobalConfig.resources.resourcePrecision; + } + getResourceMultiplier() { return EternumGlobalConfig.resources.resourceMultiplier; } diff --git a/client/src/dojo/modelManager/TileManager.ts b/client/src/dojo/modelManager/TileManager.ts index 34b38cc7a..86349766a 100644 --- a/client/src/dojo/modelManager/TileManager.ts +++ b/client/src/dojo/modelManager/TileManager.ts @@ -208,38 +208,36 @@ export class TileManager { const populationOverrideId = uuid(); - const realmEntityId = getEntityIdFromKeys([BigInt(entityId)]); + const realmEntity = getEntityIdFromKeys([BigInt(entityId)]); this.setup.components.Population.addOverride(populationOverrideId, { - entity: realmEntityId, + entity: realmEntity, value: { population: - (getComponentValue(this.setup.components.Population, realmEntityId)?.population || 0) + + (getComponentValue(this.setup.components.Population, realmEntity)?.population || 0) + configManager.getBuildingPopConfig(buildingType).population, capacity: - (getComponentValue(this.setup.components.Population, realmEntityId)?.capacity || 0) + + (getComponentValue(this.setup.components.Population, realmEntity)?.capacity || 0) + configManager.getBuildingPopConfig(buildingType).capacity, }, }); const quantityOverrideId = uuid(); - if (buildingType === BuildingType.Storehouse) { - const storehouseQuantity = - getComponentValue( - this.setup.components.BuildingQuantityv2, - getEntityIdFromKeys([BigInt(entityId), BigInt(buildingType)]), - )?.value || 0; - - this.setup.components.BuildingQuantityv2.addOverride(quantityOverrideId, { - entity: realmEntityId, - value: { - value: storehouseQuantity + 1, - }, - }); - } + + const buildingQuantityEntity = getEntityIdFromKeys([BigInt(entityId), BigInt(buildingType)]); + + const storehouseQuantity = + getComponentValue(this.setup.components.BuildingQuantityv2, buildingQuantityEntity)?.value || 0; + + this.setup.components.BuildingQuantityv2.addOverride(quantityOverrideId, { + entity: buildingQuantityEntity, + value: { + value: storehouseQuantity + 1, + }, + }); const resourceChange = configManager.buildingCosts[buildingType]; resourceChange.forEach((resource) => { - this._overrideResource(realmEntityId, resource.resource, -BigInt(resource.amount)); + this._overrideResource(realmEntity, resource.resource, -BigInt(resource.amount)); }); return { overrideId, populationOverrideId, quantityOverrideId }; diff --git a/client/src/dojo/queries.ts b/client/src/dojo/queries.ts index ce91ddf07..fe44ebfd2 100644 --- a/client/src/dojo/queries.ts +++ b/client/src/dojo/queries.ts @@ -73,7 +73,7 @@ export const addToSubscription = async ( ...positionClause, }, components, - 10_000, + 30_000, false, )); @@ -87,7 +87,7 @@ export const addToSubscription = async ( }, }, components, - 20_000, + 30_000, false, ); }; @@ -106,7 +106,7 @@ export const addMarketSubscription = async ( }, }, components, - 50_000, + 30_000, false, ); }; diff --git a/client/src/three/scenes/Worldmap.ts b/client/src/three/scenes/Worldmap.ts index 518bec722..e5e06babb 100644 --- a/client/src/three/scenes/Worldmap.ts +++ b/client/src/three/scenes/Worldmap.ts @@ -61,6 +61,8 @@ export default class WorldmapScene extends HexagonScene { dojo: SetupResult; + private fetchedChunks: Set = new Set(); + constructor( dojoContext: SetupResult, raycaster: Raycaster, @@ -379,6 +381,7 @@ export default class WorldmapScene extends HexagonScene { useUIStore.getState().setLeftNavigationView(LeftView.None); this.armyManager.addLabelsToScene(); + this.clearTileEntityCache(); } onSwitchOff() { @@ -670,53 +673,71 @@ export default class WorldmapScene extends HexagonScene { const { width } = this.renderChunkSize; const range = width / 2; - const sub = await getEntities( - this.dojo.network.toriiClient, - { - Composite: { - operator: "And", - clauses: [ - { - Member: { - model: "s0_eternum-Tile", - member: "col", - operator: "Gte", - value: { Primitive: { U32: startCol - range } }, - }, - }, - { - Member: { - model: "s0_eternum-Tile", - member: "col", - operator: "Lte", - value: { Primitive: { U32: startCol + range } }, - }, - }, - { - Member: { - model: "s0_eternum-Tile", - member: "row", - operator: "Gte", - value: { Primitive: { U32: startRow - range } }, - }, - }, + // Create a unique key for this chunk range + const chunkKey = `${startCol - range},${startCol + range},${startRow - range},${startRow + range}`; + + console.log(chunkKey); + + // Skip if we've already fetched this chunk + if (this.fetchedChunks.has(chunkKey)) { + console.log("Already fetched"); + return; + } + + // Add to fetched chunks before the query to prevent concurrent duplicate requests + this.fetchedChunks.add(chunkKey); + + try { + await getEntities( + this.dojo.network.toriiClient, { - Member: { - model: "s0_eternum-Tile", - member: "row", - operator: "Lte", - value: { Primitive: { U32: startRow + range } }, - }, + Composite: { + operator: "And", + clauses: [ + { + Member: { + model: "s0_eternum-Tile", + member: "col", + operator: "Gte", + value: { Primitive: { U32: startCol - range } }, + }, + }, + { + Member: { + model: "s0_eternum-Tile", + member: "col", + operator: "Lte", + value: { Primitive: { U32: startCol + range } }, + }, + }, + { + Member: { + model: "s0_eternum-Tile", + member: "row", + operator: "Gte", + value: { Primitive: { U32: startRow - range } }, + }, + }, + { + Member: { + model: "s0_eternum-Tile", + member: "row", + operator: "Lte", + value: { Primitive: { U32: startRow + range } }, + }, + }, + ], + }, }, - ], - }, - }, - this.dojo.network.contractComponents as any, - 1000, - false, - ); - - console.log(sub); + this.dojo.network.contractComponents as any, + 1000, + false, + ); + } catch (error) { + // If there's an error, remove the chunk from cached set so it can be retried + this.fetchedChunks.delete(chunkKey); + console.error('Error fetching tile entities:', error); + } } private getExploredHexesForCurrentChunk() { @@ -823,4 +844,8 @@ export default class WorldmapScene extends HexagonScene { this.minimap.update(); } } + + public clearTileEntityCache() { + this.fetchedChunks.clear(); + } } diff --git a/client/src/ui/components/entities/Entity.tsx b/client/src/ui/components/entities/Entity.tsx index c853b37f5..479047c50 100644 --- a/client/src/ui/components/entities/Entity.tsx +++ b/client/src/ui/components/entities/Entity.tsx @@ -30,6 +30,9 @@ type EntityProps = { arrival: ArrivalInfo; } & React.HTMLAttributes; +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(); @@ -50,23 +53,32 @@ export const EntityArrival = ({ arrival, ...props }: EntityProps) => { useEffect(() => { if (entityResources.length === 0) { + const cacheKey = `${CACHE_KEY}-${arrival.entityId}`; + const cachedTime = localStorage.getItem(cacheKey); + const now = Date.now(); + + if (cachedTime && now - parseInt(cachedTime) < CACHE_DURATION) { + return; + } + setIsSyncing(true); const fetch = async () => { - try { - await addToSubscription( - dojo.network.toriiClient, - dojo.network.contractComponents as any, - arrival.entityId.toString(), - ); - } catch (error) { - console.error("Fetch failed", error); - } finally { - setIsSyncing(false); + try { + await addToSubscription( + dojo.network.toriiClient, + dojo.network.contractComponents as any, + arrival.entityId.toString(), + ); + localStorage.setItem(cacheKey, now.toString()); + } catch (error) { + console.error("Fetch failed", error); + } finally { + setIsSyncing(false); + } + }; + fetch(); } - }; - fetch(); - } - }, [arrival.entityId, dojo.network.toriiClient, dojo.network.contractComponents, entityResources.length]); + }, [arrival.entityId, dojo.network.toriiClient, dojo.network.contractComponents, entityResources.length]); const army = useMemo(() => getArmy(arrival.entityId), [arrival.entityId, entity.resources]); diff --git a/client/src/ui/components/list/EntityList.tsx b/client/src/ui/components/list/EntityList.tsx index c83f79076..366364463 100644 --- a/client/src/ui/components/list/EntityList.tsx +++ b/client/src/ui/components/list/EntityList.tsx @@ -17,6 +17,7 @@ interface EntityListProps { current?: ID; entityHeader: (props: { id: any }) => React.ReactElement | null; entityContent?: (props: { id: any }) => React.ReactElement | null; + chunkSize?: number; questing?: boolean; className?: string; extraBackButtonContent?: React.ReactElement; @@ -31,6 +32,7 @@ export const EntityList = ({ current, entityHeader, entityContent, + chunkSize, questing, className, extraBackButtonContent, @@ -38,6 +40,7 @@ export const EntityList = ({ }: EntityListProps) => { const [selectedEntity, setSelectedEntity] = useState(null); const [searchTerm, setSearchTerm] = useState(""); + const [displayCount, setDisplayCount] = useState(chunkSize || 20); useEffect(() => { const entity = list.find((entity) => entity.entity_id === current); @@ -52,6 +55,13 @@ export const EntityList = ({ ) .filter((entity) => !filterEntityIds || filterEntityIds.includes(entity.entity_id)); + const displayedItems = filteredList.slice(0, displayCount); + const hasMore = displayCount < filteredList.length; + + const loadMore = () => { + setDisplayCount((prev) => Math.min(prev + 20, filteredList.length)); + }; + return (
{selectedEntity ? ( @@ -73,33 +83,44 @@ export const EntityList = ({ value={searchTerm} onChange={(e) => { setSearchTerm(e.target.value); + setDisplayCount(chunkSize || 20); // Reset display count when search changes }} onKeyDown={(e) => { e.stopPropagation(); }} className="w-full p-2 mb-2 bg-gold/10 border border-gold/20 rounded text-gold placeholder-gold/50 focus:outline-none focus:border-gold/40" /> -
    - {filteredList.map((entity) => ( -
  • setSelectedEntity(entity)} - > -
    - {entityHeader(entity.id)} +
    +
      + {displayedItems.map((entity) => ( +
    • setSelectedEntity(entity)} + > +
      + {entityHeader(entity.id)} - {entity.id !== Number(DUMMY_HYPERSTRUCTURE_ENTITY_ID) && ( -
      - {entityContent && entityContent(entity.id)} -
      - )} -
      -
    • - ))} -
    + {entity.id !== Number(DUMMY_HYPERSTRUCTURE_ENTITY_ID) && ( +
    + {entityContent && entityContent(entity.id)} +
    + )} +
    +
  • + ))} +
+ {hasMore && ( +
+ +
+ )} +
)} diff --git a/client/src/ui/components/resources/InventoryResources.tsx b/client/src/ui/components/resources/InventoryResources.tsx index 29ee90e6d..8136bef67 100644 --- a/client/src/ui/components/resources/InventoryResources.tsx +++ b/client/src/ui/components/resources/InventoryResources.tsx @@ -4,7 +4,10 @@ import { useResourceBalance, useResourcesUtils } from "@/hooks/helpers/useResour import { ResourceCost } from "@/ui/elements/ResourceCost"; import { divideByPrecision } from "@/ui/utils/utils"; import { ID, Resource, ResourcesIds } from "@bibliothecadao/eternum"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; + +const CACHE_KEY = "inventory-resources-sync"; +const CACHE_DURATION = 2 * 60 * 1000; // 2 minutes in milliseconds export const InventoryResources = ({ entityId, @@ -36,25 +39,31 @@ export const InventoryResources = ({ [dynamic, entityId, getBalance], ); - useEffect(() => { + useMemo(async () => { if (inventoriesResources.length === 0) { + const cacheKey = `${CACHE_KEY}-${entityId}`; + const cachedTime = localStorage.getItem(cacheKey); + const now = Date.now(); + + if (cachedTime && now - parseInt(cachedTime) < CACHE_DURATION) { + return; + } + setIsSyncing(true); - const fetch = async () => { - try { - await addToSubscription( - dojo.network.toriiClient, - dojo.network.contractComponents as any, - entityId.toString(), - ); - } catch (error) { - console.error("Fetch failed", error); - } finally { - setIsSyncing(false); - } - }; - fetch(); + try { + await addToSubscription( + dojo.network.toriiClient, + dojo.network.contractComponents as any, + entityId.toString(), + ); + localStorage.setItem(cacheKey, now.toString()); + } catch (error) { + console.error("Fetch failed", error); + } finally { + setIsSyncing(false); + } } - }, [inventoriesResources.length, entityId]); + }, [inventoriesResources.length, entityId, dojo.network.toriiClient, dojo.network.contractComponents]); const allResources = [...inventoriesResources, ...dynamicResources]; diff --git a/client/src/ui/components/resources/TravelInfo.tsx b/client/src/ui/components/resources/TravelInfo.tsx index d5f62b8d3..e27d6deaa 100644 --- a/client/src/ui/components/resources/TravelInfo.tsx +++ b/client/src/ui/components/resources/TravelInfo.tsx @@ -2,9 +2,15 @@ import { configManager } from "@/dojo/setup"; import { useResourceBalance } from "@/hooks/helpers/useResources"; import { GRAMS_PER_KG } from "@/ui/constants"; import { ResourceIcon } from "@/ui/elements/ResourceIcon"; -import { currencyFormat, divideByPrecision, getTotalResourceWeight, multiplyByPrecision } from "@/ui/utils/utils"; -import { CapacityConfigCategory, ResourcesIds, type ID, type Resource } from "@bibliothecadao/eternum"; -import { useEffect, useState } from "react"; +import { + calculateDonkeysNeeded, + currencyFormat, + divideByPrecision, + getTotalResourceWeight, + multiplyByPrecision, +} from "@/ui/utils/utils"; +import { ResourcesIds, type ID, type Resource } from "@bibliothecadao/eternum"; +import { useEffect, useMemo, useState } from "react"; export const TravelInfo = ({ entityId, @@ -21,20 +27,22 @@ export const TravelInfo = ({ }) => { const [resourceWeight, setResourceWeight] = useState(0); const [donkeyBalance, setDonkeyBalance] = useState(0); - const neededDonkeys = Math.ceil( - divideByPrecision(resourceWeight) / configManager.getCapacityConfig(CapacityConfigCategory.Donkey), - ); + 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 currentDonkeyAmount = isAmm ? 0 : resources.find((r) => r.resourceId === ResourcesIds.Donkey)?.amount || 0; + const calculatedDonkeyBalance = divideByPrecision(balance) - currentDonkeyAmount; + setDonkeyBalance(calculatedDonkeyBalance); if (setCanCarry) { diff --git a/client/src/ui/components/trading/MarketOrderPanel.tsx b/client/src/ui/components/trading/MarketOrderPanel.tsx index 4e7cb4e88..7120b3fbe 100644 --- a/client/src/ui/components/trading/MarketOrderPanel.tsx +++ b/client/src/ui/components/trading/MarketOrderPanel.tsx @@ -11,6 +11,7 @@ import Button from "@/ui/elements/Button"; import { NumberInput } from "@/ui/elements/NumberInput"; import { ResourceIcon } from "@/ui/elements/ResourceIcon"; import { + calculateDonkeysNeeded, currencyFormat, divideByPrecision, formatNumber, @@ -692,7 +693,3 @@ const OrderCreation = ({ ); }; - -const calculateDonkeysNeeded = (orderWeight: number): number => { - return Math.ceil(divideByPrecision(orderWeight) / configManager.getCapacityConfig(CapacityConfigCategory.Donkey)); -}; diff --git a/client/src/ui/layouts/World.tsx b/client/src/ui/layouts/World.tsx index f8c80fdca..87c966e09 100644 --- a/client/src/ui/layouts/World.tsx +++ b/client/src/ui/layouts/World.tsx @@ -150,7 +150,6 @@ export const World = ({ backgroundImage }: { backgroundImage: string }) => { dojo.network.toriiClient, dojo.network.contractComponents as any, ADMIN_BANK_ENTITY_ID.toString(), - { x: 0, y: 0 }, ); } catch (error) { console.error("Fetch failed", error); @@ -160,13 +159,13 @@ export const World = ({ backgroundImage }: { backgroundImage: string }) => { console.log("world loading", worldLoading); - try { - await addMarketSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any); - } catch (error) { - console.error("Fetch failed", error); - } finally { - setMarketLoading(false); - } + try { + await addMarketSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any); + } catch (error) { + console.error("Fetch failed", error); + } finally { + setMarketLoading(false); + } }; fetch(); diff --git a/client/src/ui/modules/navigation/LeftNavigationModule.tsx b/client/src/ui/modules/navigation/LeftNavigationModule.tsx index 56a3b3b52..2d0710713 100644 --- a/client/src/ui/modules/navigation/LeftNavigationModule.tsx +++ b/client/src/ui/modules/navigation/LeftNavigationModule.tsx @@ -12,7 +12,7 @@ import { motion } from "framer-motion"; import { Suspense, lazy, memo, useEffect, useMemo } from "react"; import { construction, military, trade, worldStructures } from "../../components/navigation/Config"; import CircleButton from "../../elements/CircleButton"; -import { EventStream } from "../stream/EventStream"; +import { Chat } from "../chat/Chat"; const EntityDetails = lazy(() => import("../entity-details/EntityDetails").then((module) => ({ default: module.EntityDetails })), @@ -259,8 +259,8 @@ export const LeftNavigationModule = memo(() => { {!IS_MOBILE && (
- {/* */} - + + {/* */}
)} diff --git a/client/src/ui/modules/navigation/QuestMenu.tsx b/client/src/ui/modules/navigation/QuestMenu.tsx index 7f069fcbe..6258e7f60 100644 --- a/client/src/ui/modules/navigation/QuestMenu.tsx +++ b/client/src/ui/modules/navigation/QuestMenu.tsx @@ -1,5 +1,5 @@ import { useDojo } from "@/hooks/context/DojoContext"; -import { Prize, QuestStatus, useQuests } from "@/hooks/helpers/useQuests"; +import { Prize, QuestStatus, useQuests, useUnclaimedQuestsCount } from "@/hooks/helpers/useQuests"; import { useRealm } from "@/hooks/helpers/useRealm"; import useUIStore from "@/hooks/store/useUIStore"; import { useWorldStore } from "@/hooks/store/useWorldLoading"; @@ -11,7 +11,7 @@ import { QuestType } from "@bibliothecadao/eternum"; import clsx from "clsx"; import { useState } from "react"; -export const QuestsMenu = ({ unclaimedQuestsCount }: { unclaimedQuestsCount: number }) => { +export const QuestsMenu = () => { const { account: { account }, setup: { @@ -34,6 +34,9 @@ export const QuestsMenu = ({ unclaimedQuestsCount }: { unclaimedQuestsCount: num const { handleStart } = useTutorial(questSteps.get(currentQuest?.id || QuestType.Settle)); + const isWorldLoading = useWorldStore((state) => state.isWorldLoading); + const { unclaimedQuestsCount } = useUnclaimedQuestsCount(); + const [isLoading, setIsLoading] = useState(false); const [skipQuest, setSkipQuest] = useState(false); @@ -102,96 +105,101 @@ export const QuestsMenu = ({ unclaimedQuestsCount }: { unclaimedQuestsCount: num }; return ( -
- - -
-
-
- - - -
-
-
- - {skipQuest ? ( -
- + +
+
+
+ + +
+
+
+ + {skipQuest ? ( +
+ + + +
+ ) : ( + + )}
- ) : ( - - )} -
+
+ ) ); }; diff --git a/client/src/ui/modules/navigation/TopLeftNavigation.tsx b/client/src/ui/modules/navigation/TopLeftNavigation.tsx index c6bccb01a..fc70dab97 100644 --- a/client/src/ui/modules/navigation/TopLeftNavigation.tsx +++ b/client/src/ui/modules/navigation/TopLeftNavigation.tsx @@ -2,7 +2,6 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/DojoContext"; import { useEntities, useEntitiesUtils } from "@/hooks/helpers/useEntities"; import { useQuery } from "@/hooks/helpers/useQuery"; -import { useUnclaimedQuestsCount } from "@/hooks/helpers/useQuests"; import useUIStore from "@/hooks/store/useUIStore"; import useNextBlockTimestamp from "@/hooks/useNextBlockTimestamp"; import { soundSelector, useUiSounds } from "@/hooks/useUISound"; @@ -96,7 +95,6 @@ const WorkersHutTooltipContent = () => { export const TopLeftNavigation = memo(() => { const { setup } = useDojo(); - const { unclaimedQuestsCount } = useUnclaimedQuestsCount(); const { isMapView, handleUrlChange, hexPosition } = useQuery(); const { playerStructures } = useEntities(); const { getEntityInfo } = useEntitiesUtils(); @@ -321,11 +319,7 @@ export const TopLeftNavigation = memo(() => {
- {unclaimedQuestsCount > 0 && ( -
- -
- )} +
); diff --git a/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx b/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx index 90428b3cc..db712e55f 100644 --- a/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx +++ b/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx @@ -34,105 +34,74 @@ import { Component, useCallback, useEffect, useMemo, useState } from "react"; import { Tabs } from "../../elements/tab"; export const WorldStructuresMenu = ({ className }: { className?: string }) => { - const dojo = useDojo(); const { account: { account }, - } = dojo; + network: { toriiClient, contractComponents }, + } = useDojo(); useEffect(() => { - const fetch = async () => { + const fetchData = async () => { try { - await fetchHyperstructureData(dojo.network.toriiClient, dojo.network.contractComponents as any); + await fetchHyperstructureData(toriiClient, contractComponents as any); } catch (error) { - console.error("Fetch failed", error); + console.error("Failed to fetch hyperstructure data:", error); } }; - - fetch(); - }, []); + fetchData(); + }, [toriiClient, contractComponents]); const [selectedTab, setSelectedTab] = useState(0); const [showOnlyMine, setShowOnlyMine] = useState(false); const { hyperstructures } = useHyperstructures(); const { fragmentMines } = useFragmentMines(); - const myHyperstructures = useGetHyperstructuresWithContributionsFromPlayer(); - const hyperstructureExtraContent = useCallback( - (entityId: any) => { - const hyperstructure = hyperstructures.find((hyperstructure) => hyperstructure.entity_id === entityId); - if (!hyperstructure) return null; - return ( - - ); - }, - [hyperstructures], - ); - - const fragmentMineExtraContent = useCallback( - (entityId: any) => { - const fragmentMine = fragmentMines.find((fragmentMine) => fragmentMine.entity_id === entityId); - if (!fragmentMine) return null; - - return ; - }, - [fragmentMines], - ); - - const hyperstructureEntityHeader = useCallback( - (entityId: any) => { - const entity = hyperstructures.find((hyperstructure) => hyperstructure.entity_id === entityId); + const renderExtraContent = useCallback( + (entityId: ID, type: "hyperstructure" | "fragmentMine") => { + const entities = type === "hyperstructure" ? hyperstructures : fragmentMines; + const entity = entities.find((e) => e.entity_id === entityId); if (!entity) return null; - return ; + return type === "hyperstructure" ? ( + + ) : ( + + ); }, - [hyperstructures], + [hyperstructures, fragmentMines], ); - const fragmentMineEntityHeader = useCallback( - (entityId: any) => { - const entity = fragmentMines.find((fragmentMine) => fragmentMine.entity_id === entityId); - if (!entity) return null; - - return ; + const renderEntityHeader = useCallback( + (entityId: ID, type: "hyperstructure" | "fragmentMine") => { + const entities = type === "hyperstructure" ? hyperstructures : fragmentMines; + const entity = entities.find((e) => e.entity_id === entityId); + return entity ? : null; }, - [fragmentMines], + [hyperstructures, fragmentMines], ); const tabs = useMemo( () => [ { key: "Hyperstructures", - label: ( -
-
Hyperstructures
-
- ), + label: "Hyperstructures", component: ( <> -
- -
+ } - entityHeader={hyperstructureEntityHeader} - entityContent={hyperstructureExtraContent} + entityHeader={(id: any) => renderEntityHeader(id, "hyperstructure")} + entityContent={(id: any) => renderExtraContent(id, "hyperstructure")} + chunkSize={10} list={hyperstructures - .filter((hyperstructure) => hyperstructure.created_at) + .filter((h) => h.created_at) .sort((a, b) => Number(a.entity_id) - Number(b.entity_id)) - .map((hyperstructure) => ({ - id: hyperstructure.entity_id, - position: { x: hyperstructure.x, y: hyperstructure.y }, - ...hyperstructure, + .map((h) => ({ + id: h.entity_id, + position: { x: h.x, y: h.y }, + ...h, }))} filterEntityIds={showOnlyMine ? Array.from(myHyperstructures()) : undefined} /> @@ -141,38 +110,26 @@ export const WorldStructuresMenu = ({ className }: { className?: string }) => { }, { key: "Mines", - label: ( -
-
Mines
-
- ), + label: "Mines", component: ( <> -
- -
+ } - entityHeader={fragmentMineEntityHeader} - entityContent={fragmentMineExtraContent} + entityHeader={(id: any) => renderEntityHeader(id, "fragmentMine")} + entityContent={(id: any) => renderExtraContent(id, "fragmentMine")} + chunkSize={10} list={fragmentMines .sort((a, b) => Number(a.entity_id) - Number(b.entity_id)) - .map((fragmentMine) => ({ - id: fragmentMine.entity_id, - position: { x: fragmentMine.x, y: fragmentMine.y }, - ...fragmentMine, + .map((m) => ({ + id: m.entity_id, + position: { x: m.x, y: m.y }, + ...m, }))} filterEntityIds={ showOnlyMine - ? (fragmentMines - .filter((mine) => { - return mine.owner === account.address; - }) - .map((mine) => mine.entity_id) as ID[]) + ? fragmentMines.filter((m) => m.owner === account.address).map((m) => m.entity_id as ID) : undefined } /> @@ -180,21 +137,20 @@ export const WorldStructuresMenu = ({ className }: { className?: string }) => { ), }, ], - [selectedTab, hyperstructures, fragmentMines], + [selectedTab, hyperstructures, fragmentMines, showOnlyMine, account.address, myHyperstructures], ); return ( <> - setSelectedTab(index)} - variant="default" - className="" - > + {tabs.map((tab, index) => ( - {tab.label} + +
+
{tab.label}
+
+
))}
@@ -207,6 +163,21 @@ export const WorldStructuresMenu = ({ className }: { className?: string }) => { ); }; +const FilterCheckbox = ({ + showOnlyMine, + setShowOnlyMine, +}: { + showOnlyMine: boolean; + setShowOnlyMine: (show: boolean) => void; +}) => ( +
+ +
+); + const BaseStructureExtraContent = ({ x, y, @@ -228,44 +199,29 @@ const BaseStructureExtraContent = ({ const ownerName = getAddressNameFromEntity(entityId); const address = getPlayerAddressFromEntity(entityId); const guildName = getGuildFromPlayerAddress(address || 0n)?.name; - return { - name: ownerName, - guildName, - }; - }, []); + return { name: ownerName, guildName }; + }, [entityId, getAddressNameFromEntity, getPlayerAddressFromEntity, getGuildFromPlayerAddress]); - const defensiveArmy = useMemo(() => { - const army = armies.find((army) => army.protectee?.protectee_id); - const ownerName = getAddressNameFromEntity(army?.entity_id || 0); - const guildName = getGuildFromPlayerAddress(army?.owner?.address || 0n)?.name; - return { - totalTroops: - (army?.troops?.knight_count || 0n) + - (army?.troops?.paladin_count || 0n) + - (army?.troops?.crossbowman_count || 0n), - army, - name: ownerName, - guildName, + const { defensiveArmy, attackingArmy } = useMemo(() => { + const defensive = armies.find((army) => army.protectee?.protectee_id); + const attacking = armies.find( + (army) => army.battle_side === BattleSide[BattleSide.Attack] && army.battle_id === defensive?.battle_id, + ); + + const getArmyInfo = (army?: any) => { + if (!army) return; + const ownerName = getAddressNameFromEntity(army.entity_id || 0); + 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); + return { totalTroops, army, name: ownerName, guildName }; }; - }, [armies]); - const attackingArmy = useMemo(() => { - const army = armies.find( - (army) => army.battle_side === BattleSide[BattleSide.Attack] && army.battle_id === defensiveArmy.army?.battle_id, - ); - if (!army) return; - const ownerName = getAddressNameFromEntity(army?.entity_id || 0); - const guildName = getGuildFromPlayerAddress(army?.owner?.address || 0n)?.name; return { - totalTroops: - (army?.troops?.knight_count || 0n) + - (army?.troops?.paladin_count || 0n) + - (army?.troops?.crossbowman_count || 0n), - army, - name: ownerName, - guildName, + defensiveArmy: getArmyInfo(defensive) || { totalTroops: 0n }, + attackingArmy: getArmyInfo(attacking), }; - }, [armies]); + }, [armies, getAddressNameFromEntity, getGuildFromPlayerAddress]); return (
@@ -280,7 +236,7 @@ const BaseStructureExtraContent = ({
Battle: - {attackingArmy ? `${attackingArmy?.guildName || attackingArmy?.name || "Mercenaries"}⚔` : "None"} + {attackingArmy ? `${attackingArmy.guildName || attackingArmy.name || "Mercenaries"}⚔` : "None"}
{children} @@ -297,16 +253,16 @@ const HyperStructureExtraContent = ({ x: number; y: number; }) => { - const dojo = useDojo(); const { account: { account }, - } = dojo; + } = useDojo(); const progress = useHyperstructureProgress(hyperstructureEntityId); - - const latestChangeEvent = LeaderboardManager.instance(dojo).getCurrentCoOwners(hyperstructureEntityId); - + const latestChangeEvent = LeaderboardManager.instance(useDojo()).getCurrentCoOwners(hyperstructureEntityId); const needTosetCoOwners = !latestChangeEvent && progress.percentage === 100; + const shares = + LeaderboardManager.instance(useDojo()).getAddressShares(ContractAddress(account.address), hyperstructureEntityId) || + 0; return ( @@ -317,16 +273,7 @@ const HyperStructureExtraContent = ({
Shares: - - {currencyIntlFormat( - (LeaderboardManager.instance(dojo).getAddressShares( - ContractAddress(account.address), - hyperstructureEntityId, - ) || 0) * 100, - 0, - )} - % - + {currencyIntlFormat(shares * 100, 0)}%
); @@ -334,7 +281,7 @@ const HyperStructureExtraContent = ({ const FragmentMineExtraContent = ({ x, y, entityId }: { x: number; y: number; entityId: ID }) => { const { getBalance } = useResourceBalance(); - const dynamicResources = getBalance(entityId, ResourcesIds.AncientFragment); + const { balance } = getBalance(entityId, ResourcesIds.AncientFragment); const trait = useMemo(() => findResourceById(ResourcesIds.AncientFragment)?.trait, []); return ( @@ -345,8 +292,8 @@ const FragmentMineExtraContent = ({ x, y, entityId }: { x: number; y: number; en {Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1, - }).format(divideByPrecision(dynamicResources.balance || 0))} - + }).format(divideByPrecision(balance || 0))} +
@@ -355,31 +302,28 @@ const FragmentMineExtraContent = ({ x, y, entityId }: { x: number; y: number; en const EntityHeader = ({ entity }: { entity: any }) => { const position = { x: entity.x, y: entity.y }; - const access = entity?.access ? DisplayedAccess[entity.access as keyof typeof DisplayedAccess] : undefined; + const getAccessStyle = (access?: string) => { + if (!access) return ""; + const styles = { + Public: "text-green border border-green", + Private: "text-red border border-red", + "Tribe Only": "text-gold border border-gold", + }; + return styles[access as keyof typeof styles] || ""; + }; + return (
{entity.name}
{access && ( - - {access} - + {access} )} -
- +
+
diff --git a/client/src/ui/utils/utils.tsx b/client/src/ui/utils/utils.tsx index 648e27e38..cd49125a3 100644 --- a/client/src/ui/utils/utils.tsx +++ b/client/src/ui/utils/utils.tsx @@ -1,17 +1,18 @@ import { type ClientComponents } from "@/dojo/createClientComponents"; import { ClientConfigManager } from "@/dojo/modelManager/ConfigManager"; import { HEX_SIZE } from "@/three/scenes/constants"; -import { type HexPosition, ResourceMiningTypes } from "@/types"; +import { ResourceMiningTypes, type HexPosition } from "@/types"; import { BuildingType, + CapacityConfigCategory, ContractAddress, EternumGlobalConfig, - type ID, - type Position, - type Resource, ResourceCost, ResourcesIds, TickIds, + type ID, + type Position, + type Resource, } from "@bibliothecadao/eternum"; import { type ComponentValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; @@ -499,3 +500,10 @@ export const getRandomBackgroundImage = () => { export const adjustWonderLordsCost = (cost: ResourceCost[]): ResourceCost[] => { return cost.map((item) => (item.resource === ResourcesIds.Lords ? { ...item, amount: item.amount * 0.1 } : item)); }; + +export const calculateDonkeysNeeded = (orderWeight: number): number => { + const configManager = ClientConfigManager.instance(); + const donkeyCapacityGrams = configManager.getCapacityConfig(CapacityConfigCategory.Donkey); + + return Math.ceil((orderWeight / 1000) / donkeyCapacityGrams); +}; \ No newline at end of file diff --git a/docs/pages/mechanics/military/units.mdx b/docs/pages/mechanics/military/units.mdx index 96f755026..1c25c4746 100644 --- a/docs/pages/mechanics/military/units.mdx +++ b/docs/pages/mechanics/military/units.mdx @@ -68,5 +68,5 @@ import { STAMINA_REFILL_PER_TICK } from "@bibliothecadao/eternum"; - Three attacking armies can be created to start but increases as realm upgrades - Explores map, can initiate battle and can join a side in an existing battle - Can only transfer military to armies on the same hex -- Armies have limited storage and cannot move if storage is full, stamina is depleted or there is not enough food at the - home realm +- Armies can't move if their stamina is depleted or there is not enough food at the home realm +- Armies have limited storage and cannot explore if storage is full, but they can retrace back to the home realm to unload diff --git a/landing/.env.preview b/landing/.env.preview index aa5a5aaab..11ee75716 100644 --- a/landing/.env.preview +++ b/landing/.env.preview @@ -3,13 +3,13 @@ VITE_PUBLIC_MASTER_PRIVATE_KEY=0x075362a844768f31c8058ce31aec3dd7751686440b4f220 VITE_PUBLIC_WORLD_ADDRESS="0x00fd85ef42eaed3b90d02d2cdc7417d6cae189ff4ba876aa5608551afbf1fb47" VITE_PUBLIC_ACCOUNT_CLASS_HASH="0x07dc7899aa655b0aae51eadff6d801a58e97dd99cf4666ee59e704249e51adf2" VITE_PUBLIC_FEE_TOKEN_ADDRESS=0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 -VITE_PUBLIC_TORII=https://api.cartridge.gg/x/sepolia-rc-17/torii +VITE_PUBLIC_TORII=https://api.cartridge.gg/x/sepolia-rc-18/torii VITE_PUBLIC_NODE_URL=https://api.cartridge.gg/x/starknet/sepolia VITE_PUBLIC_DEV=true VITE_PUBLIC_GAME_VERSION="v1.0.0-rc7" VITE_PUBLIC_SHOW_FPS=false VITE_PUBLIC_GRAPHICS_DEV=false -VITE_PUBLIC_TORII_RELAY=/dns4/api.cartridge.gg/tcp/443/x-parity-wss/%2Fx%2Fsepolia-rc-17%2Ftorii%2Fwss +VITE_PUBLIC_TORII_RELAY=/dns4/api.cartridge.gg/tcp/443/x-parity-wss/%2Fx%2Fsepolia-rc-18%2Ftorii%2Fwss VITE_SEASON_PASS_ADDRESS=0x23cc88996a5f9c7bcb559fdcffc257c0f75abe60f2a7e5d5cd343f8a95967f7 VITE_REALMS_ADDRESS=0x3205f47bd6f0b5e9cd5c79fcae19e12523a024709776d0a9e8b375adf63468d VITE_LORDS_ADDRESS=0x0342ad5cc14002c005a5cedcfce2bd3af98d5e7fb79e9bf949b3a91cf145d72e diff --git a/landing/src/components/modules/bridge-out-step-1.tsx b/landing/src/components/modules/bridge-out-step-1.tsx index 53e637f68..48705736e 100644 --- a/landing/src/components/modules/bridge-out-step-1.tsx +++ b/landing/src/components/modules/bridge-out-step-1.tsx @@ -22,7 +22,12 @@ import { Button } from "../ui/button"; import { ResourceIcon } from "../ui/elements/ResourceIcon"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; import { Tooltip, TooltipProvider } from "../ui/tooltip"; -import { calculateDonkeysNeeded, getSeasonAddresses, getTotalResourceWeight } from "../ui/utils/utils"; +import { + calculateDonkeysNeeded, + divideByPrecision, + getSeasonAddresses, + getTotalResourceWeight, +} from "../ui/utils/utils"; import { BridgeFees } from "./bridge-fees"; function formatFee(fee: number) { @@ -259,7 +264,8 @@ export const BridgeOutStep1 = () => {
- {donkeysNeeded} / {donkeyBalance.balance} + {donkeysNeeded} / {divideByPrecision(donkeyBalance.balance)}{" "} +
{ const [isCompactGrid, setIsCompactGrid] = useState(false); - if (!realms?.length) return
No Realms found
; + if (!realms?.length) { + return ( +
+ {/* Decorative elements */} +
+
+
+
+ + +
+

+ No Realms Yet +

+ +

+ Your collection of realms will appear here once you acquire them. + + Get your realm to start your journey into Realms World! +

+ + + + +
+
+ ); + } const gridItems: RealmGridItem[] = realms.map((realm) => ({ colSpan: isCompactGrid ? { sm: 3, md: 2, lg: 2 } : { sm: 5, md: 3, lg: 3 }, data: realm!, diff --git a/landing/src/components/modules/season-passes-grid.tsx b/landing/src/components/modules/season-passes-grid.tsx index c577a9772..92f3726e9 100644 --- a/landing/src/components/modules/season-passes-grid.tsx +++ b/landing/src/components/modules/season-passes-grid.tsx @@ -1,6 +1,6 @@ import { Button } from "@/components/ui/button"; import { GetRealmsQuery } from "@/hooks/gql/graphql"; -import { Grid2X2, Grid3X3 } from "lucide-react"; +import { Crown, Grid2X2, Grid3X3 } from "lucide-react"; import { useState } from "react"; import { AnimatedGrid } from "./animated-grid"; import { SeasonPassCard } from "./season-pass-card"; @@ -23,7 +23,51 @@ interface SeasonPassRowProps { export const SeasonPassesGrid = ({ toggleNftSelection, isNftSelected, seasonPasses }: SeasonPassRowProps) => { const [isCompactGrid, setIsCompactGrid] = useState(false); - if (!seasonPasses?.length) return
No Season Pass Found
; + if (!seasonPasses?.length) { + return ( +
+
+
+
+
+ + + +
+

+ No Season Passes Yet +

+ +
+

+ Your collection of season passes will appear here until you burn them to mint a realm in the game. +

+

+ Once you burn a pass, it will be removed from this view. +

+ +

+ Get your pass to join the game and start your journey into Eternum! +

+
+ + + + +
+
+ ); + } const gridItems: RealmGridItem[] = seasonPasses.map((pass) => ({ colSpan: isCompactGrid ? { sm: 3, md: 2, lg: 2 } : { sm: 5, md: 3, lg: 3 }, diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 1140351ff..7547e3e47 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -68,7 +68,7 @@ echo "Migrating world..." sozo migrate --profile mainnet --fee eth echo "Setting up remote indexer on slot..." -slot deployments create -t epic eternum-mainnet-5 torii --version preview--91c76d9 --world 0x06a9e4c6f0799160ea8ddc43ff982a5f83d7f633e9732ce42701de1288ff705f --rpc https://api.cartridge.gg/x/starknet/mainnet --indexing.pending true --config ./torii.toml +slot deployments create -t epic sepolia-rc-18 torii --version v1.0.7 --world 0x3dc74e8caadbde233bb750a6608e095daba2891d9784ea0fb7fbf9988948c15 --rpc https://api.cartridge.gg/x/starknet/sepolia --indexing.pending true --config ./torii-sepolia.toml echo "Setting up config..."