diff --git a/client/src/hooks/helpers/useQuests.tsx b/client/src/hooks/helpers/useQuests.tsx new file mode 100644 index 000000000..16c56d13a --- /dev/null +++ b/client/src/hooks/helpers/useQuests.tsx @@ -0,0 +1,327 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { BuildingType, QuestType, StructureType } from "@bibliothecadao/eternum"; +import { useDojo } from "../context/DojoContext"; +import { useEntityQuery } from "@dojoengine/react"; +import { HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import { useEntities } from "./useEntities"; +import { ArmyInfo, useArmiesByEntityOwner } from "./useArmies"; +import { useGetMyOffers } from "./useTrade"; +import { getPillageEvents } from "@/dojo/events/pillageEventQueries"; +import { QuestId, questDetails } from "@/ui/components/quest/questDetails"; + +export interface Quest { + id: QuestId; + name: string; + description: string; + steps: string[]; + prizes: Prize[]; + depth: number; + status: QuestStatus; +} + +export interface Prize { + id: QuestType; + title: string; +} + +export enum QuestStatus { + InProgress, + Completed, + Claimed, +} + +export const useQuests = () => { + const questDependencies = useQuestDependencies(); + + const createQuest = useCallback( + (questId: QuestId) => { + const dependency = questDependencies[questId]; + return { + id: questId, + ...questDetails.get(questId)!, + status: dependency.status, + }; + }, + [questDependencies], + ); + + const quests = useMemo( + () => [ + createQuest(QuestId.Settle), + createQuest(QuestId.BuildFarm), + createQuest(QuestId.BuildResource), + createQuest(QuestId.CreateTrade), + createQuest(QuestId.CreateArmy), + createQuest(QuestId.Travel), + createQuest(QuestId.BuildWorkersHut), + createQuest(QuestId.Market), + createQuest(QuestId.Pillage), + createQuest(QuestId.Mine), + createQuest(QuestId.Contribution), + createQuest(QuestId.Hyperstructure), + ], + [createQuest], + ); + + return { quests }; +}; + +const useQuestDependencies = () => { + const { + setup: { + components: { Contribution, EntityOwner }, + }, + account: { account }, + } = useDojo(); + + const { playerRealms } = useEntities(); + const realm = useMemo(() => playerRealms()[0], [playerRealms]); + const realmEntityId = useMemo(() => realm?.entity_id, [realm]); + + const entityUpdate = useEntityQuery([HasValue(EntityOwner, { entity_owner_id: BigInt(realmEntityId || "0") })]); + + const buildingQuantities = useBuildingQuantities(realmEntityId); + + const { entityArmies } = useArmiesByEntityOwner({ entity_owner_entity_id: realmEntityId || BigInt("0") }); + const orders = useGetMyOffers(); + + const hasTroops = useMemo(() => armyHasTroops(entityArmies), [entityArmies]); + const hasTraveled = useMemo(() => armyHasTraveled(entityArmies, realm?.position), [entityArmies, realm?.position]); + + const [pillageHistoryLength, setPillageHistoryLength] = useState(0); + + useEffect(() => { + const fetchPillageHistory = async () => { + const eventsLength = await getPillageEvents(BigInt(realmEntityId ?? "0")); + setPillageHistoryLength(eventsLength); + }; + fetchPillageHistory(); + }, [realmEntityId]); + + const { playerStructures } = useEntities(); + const structures = playerStructures(); + + const countStructuresByCategory = useCallback( + (category: string) => { + return structures.filter((structure) => structure.category === category).length; + }, + [structures], + ); + + const fragmentMines = useMemo( + () => countStructuresByCategory(StructureType[StructureType.FragmentMine]), + [realmEntityId], + ); + + const hyperstructures = useMemo( + () => countStructuresByCategory(StructureType[StructureType.Hyperstructure]), + [realmEntityId], + ); + + const hyperstructureContributions = useMemo( + () => runQuery([HasValue(Contribution, { player_address: BigInt(account.address) })]).size, + [realmEntityId], + ); + + const { questClaimStatus } = useQuestClaimStatus(); + const { unclaimedQuestsCount } = useUnclaimedQuestsCount(); + + return useMemo( + () => ({ + [QuestId.Settle]: { + value: true, + status: questClaimStatus[QuestId.Settle] ? QuestStatus.Claimed : QuestStatus.Completed, + }, + [QuestId.BuildFarm]: { + value: questClaimStatus[QuestId.BuildFarm] ? null : buildingQuantities.farms, + status: + buildingQuantities.farms > 0 + ? questClaimStatus[QuestId.BuildFarm] + ? QuestStatus.Claimed + : QuestStatus.Completed + : QuestStatus.InProgress, + }, + [QuestId.BuildResource]: { + value: questClaimStatus[QuestId.BuildResource] ? null : buildingQuantities.resource, + status: + buildingQuantities.resource > 0 + ? questClaimStatus[QuestId.BuildResource] + ? QuestStatus.Claimed + : QuestStatus.Completed + : QuestStatus.InProgress, + }, + [QuestId.CreateTrade]: { + value: questClaimStatus[QuestId.CreateTrade] ? null : orders.length, + status: + orders.length > 0 + ? questClaimStatus[QuestId.CreateTrade] + ? QuestStatus.Claimed + : QuestStatus.Completed + : QuestStatus.InProgress, + }, + [QuestId.CreateArmy]: { + value: questClaimStatus[QuestId.CreateArmy] + ? { armyCount: null, hasTroops: null } + : { armyCount: entityArmies.length, hasTroops }, + status: + entityArmies.length > 0 && hasTroops + ? questClaimStatus[QuestId.CreateArmy] + ? QuestStatus.Claimed + : QuestStatus.Completed + : QuestStatus.InProgress, + }, + [QuestId.Travel]: { + value: questClaimStatus[QuestId.Travel] ? null : hasTraveled, + status: hasTraveled + ? questClaimStatus[QuestId.Travel] + ? QuestStatus.Claimed + : QuestStatus.Completed + : QuestStatus.InProgress, + }, + [QuestId.BuildWorkersHut]: { + value: questClaimStatus[QuestId.BuildWorkersHut] ? null : buildingQuantities.workersHut, + status: + buildingQuantities.workersHut > 0 + ? questClaimStatus[QuestId.BuildWorkersHut] + ? QuestStatus.Claimed + : QuestStatus.Completed + : QuestStatus.InProgress, + }, + [QuestId.Market]: { + value: questClaimStatus[QuestId.Market] ? null : buildingQuantities.markets, + status: + buildingQuantities.markets > 0 + ? questClaimStatus[QuestId.Market] + ? QuestStatus.Claimed + : QuestStatus.Completed + : QuestStatus.InProgress, + }, + [QuestId.Pillage]: { + value: questClaimStatus[QuestId.Pillage] ? null : pillageHistoryLength, + status: + pillageHistoryLength > 0 + ? questClaimStatus[QuestId.Pillage] + ? QuestStatus.Claimed + : QuestStatus.Completed + : QuestStatus.InProgress, + }, + [QuestId.Mine]: { + value: questClaimStatus[QuestId.Mine] ? null : fragmentMines, + status: + fragmentMines > 0 + ? questClaimStatus[QuestId.Mine] + ? QuestStatus.Claimed + : QuestStatus.Completed + : QuestStatus.InProgress, + }, + [QuestId.Contribution]: { + value: questClaimStatus[QuestId.Contribution] ? null : hyperstructureContributions, + status: + hyperstructureContributions > 0 + ? questClaimStatus[QuestId.Contribution] + ? QuestStatus.Claimed + : QuestStatus.Completed + : QuestStatus.InProgress, + }, + [QuestId.Hyperstructure]: { + value: questClaimStatus[QuestId.Hyperstructure] ? null : hyperstructures, + status: + hyperstructures > 0 + ? questClaimStatus[QuestId.Hyperstructure] + ? QuestStatus.Claimed + : QuestStatus.Completed + : QuestStatus.InProgress, + }, + }), + [questClaimStatus, unclaimedQuestsCount > 0 ? entityUpdate : null, unclaimedQuestsCount > 0 ? orders : null], + ); +}; + +export const useQuestClaimStatus = () => { + const { + setup: { + components: { HasClaimedStartingResources }, + }, + } = useDojo(); + + const { playerRealms } = useEntities(); + const realm = useMemo(() => playerRealms()[0], [playerRealms]); + const realmEntityId = useMemo(() => realm?.entity_id, [realm]); + + const prizeUpdate = useEntityQuery([ + HasValue(HasClaimedStartingResources, { entity_id: BigInt(realmEntityId || "0") }), + ]); + + const checkPrizesClaimed = (prizes: Prize[]) => { + return prizes.every((prize) => { + const value = getComponentValue( + HasClaimedStartingResources, + getEntityIdFromKeys([BigInt(realmEntityId || "0"), BigInt(prize.id)]), + ); + return value?.claimed; + }); + }; + + const questClaimStatus = useMemo(() => { + return Array.from(questDetails.keys()).reduce( + (acc, questName) => ({ + ...acc, + [questName]: checkPrizesClaimed(questDetails.get(questName)?.prizes || []), + }), + {} as Record, + ); + }, [prizeUpdate]); + + return { questClaimStatus }; +}; + +export const useUnclaimedQuestsCount = () => { + const { questClaimStatus } = useQuestClaimStatus(); + + const unclaimedQuestsCount = useMemo( + () => Object.values(questClaimStatus).filter((claimed) => !claimed).length, + [questClaimStatus], + ); + + return { unclaimedQuestsCount }; +}; + +const useBuildingQuantities = (realmEntityId: bigint | undefined) => { + const { + setup: { + components: { BuildingQuantityv2, EntityOwner }, + }, + } = useDojo(); + const entityUpdate = useEntityQuery([HasValue(EntityOwner, { entity_owner_id: BigInt(realmEntityId || "0") })]); + const getBuildingQuantity = (buildingType: BuildingType) => + getComponentValue(BuildingQuantityv2, getEntityIdFromKeys([BigInt(realmEntityId || "0"), BigInt(buildingType)])) + ?.value || 0; + + return useMemo( + () => ({ + farms: getBuildingQuantity(BuildingType.Farm), + resource: getBuildingQuantity(BuildingType.Resource), + workersHut: getBuildingQuantity(BuildingType.WorkersHut), + markets: getBuildingQuantity(BuildingType.Market), + }), + [realmEntityId, entityUpdate], + ); +}; + +const armyHasTroops = (entityArmies: ArmyInfo[]) => { + return ( + entityArmies && + entityArmies[0] && + (Number(entityArmies[0].troops.knight_count) != 0 || + Number(entityArmies[0].troops.crossbowman_count) != 0 || + Number(entityArmies[0].troops.paladin_count) != 0) + ); +}; + +const armyHasTraveled = (entityArmies: ArmyInfo[], realmPosition: { x: number; y: number }) => { + if (entityArmies && entityArmies[0] && realmPosition) { + return entityArmies[0].position.x != realmPosition.x || entityArmies[0].position.y != realmPosition.y; + } + return false; +}; diff --git a/client/src/hooks/helpers/useStamina.tsx b/client/src/hooks/helpers/useStamina.tsx index 3b08dea95..b33e66d04 100644 --- a/client/src/hooks/helpers/useStamina.tsx +++ b/client/src/hooks/helpers/useStamina.tsx @@ -1,4 +1,5 @@ -import { ResourcesIds, WORLD_CONFIG_ID } from "@bibliothecadao/eternum"; +import { ClientComponents } from "@/dojo/createClientComponents"; +import { EternumGlobalConfig, ResourcesIds, WORLD_CONFIG_ID } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; import { Component, Has, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; @@ -78,11 +79,24 @@ export const useStamina = () => { }); }; + const useArmiesCanMoveCount = (entityArmies: any) => { + if (!entityArmies) return 0; + + return entityArmies.filter((entity: any) => { + const stamina = getStamina({ + travelingEntityId: BigInt(entity.entity_id), + currentArmiesTick, + }); + return (stamina?.amount || 0) >= EternumGlobalConfig.stamina.travelCost; + }).length; + }; + return { optimisticStaminaUpdate, useStaminaByEntityId, getStamina, getMaxStaminaByEntityId, + useArmiesCanMoveCount, }; }; diff --git a/client/src/hooks/store/useQuestStore.tsx b/client/src/hooks/store/useQuestStore.tsx index 6288ab597..fa3ab5eef 100644 --- a/client/src/hooks/store/useQuestStore.tsx +++ b/client/src/hooks/store/useQuestStore.tsx @@ -1,349 +1,12 @@ -import { useDojo } from "@/hooks/context/DojoContext"; -import { ArmyInfo, useArmiesByEntityOwner } from "@/hooks/helpers/useArmies"; -import { useGetMyOffers } from "@/hooks/helpers/useTrade"; -import { BuildingType, QuestType, StructureType } from "@bibliothecadao/eternum"; -import { HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; -import { getEntityIdFromKeys } from "@dojoengine/utils"; -import { useCallback, useEffect, useMemo, useState } from "react"; - -import { getPillageEvents } from "@/dojo/events/pillageEventQueries"; -import { View as LeftView } from "@/ui/modules/navigation/LeftNavigationModule"; -import { useLocation } from "wouter"; import { create } from "zustand"; -import { useEntities } from "../helpers/useEntities"; -import useUIStore from "./useUIStore"; - -export enum QuestName { - Settle = "Settle", - BuildFarm = "Build a Farm", - BuildResource = "Build a Resource Facility", - CreateTrade = "Create a Trade", - CreateArmy = "Create an Army", - Travel = "Travel with your Army", - BuildWorkersHut = "Build a workers hut.", - Market = "Build a market.", - Pillage = "Pillage a structure.", - Mine = "Discover an earthenshard mine.", - Contribution = "Contribute to a hyperstructure.", - Hyperstructure = "Build a hyperstructure.", -} - -export interface Quest { - name: string; - description: string; - steps: Step[]; - completed?: boolean; - claimed?: boolean; - prizes: Prize[]; - depth: number; -} - -interface Step { - description: string; - completed: boolean; -} - -export interface Prize { - id: number; - title: string; -} +import { Quest } from "../helpers/useQuests"; export interface QuestStore { - quests: Quest[] | undefined; - setQuests: (quests: Quest[] | undefined) => void; - selectedQuest: Quest | undefined; - setSelectedQuest: (selectedQuest: Quest | undefined) => void; - claimableQuestsLength: number; - setClaimableQuestsLength: (claimableQuestsLength: number) => void; - showCompletedQuests: boolean; - setShowCompletedQuests: (showCompletedQuests: boolean) => void; + selectedQuest: Quest | null; + setSelectedQuest: (selectedQuest: Quest | null) => void; } -export const useQuestStore = create((set) => { - return { - quests: undefined, - setQuests: (quests: Quest[] | undefined) => set({ quests }), - selectedQuest: undefined, - setSelectedQuest: (selectedQuest: Quest | undefined) => set({ selectedQuest }), - claimableQuestsLength: 0, - setClaimableQuestsLength: (claimableQuestsLength: number) => set({ claimableQuestsLength }), - showCompletedQuests: false, - setShowCompletedQuests: (showCompletedQuests: boolean) => set({ showCompletedQuests }), - }; -}); - -export const useQuests = () => { - const { - setup: { - components: { BuildingQuantityv2, HasClaimedStartingResources, Contribution }, - }, - account: { account }, - } = useDojo(); - - const selectedEntity = useUIStore((state) => state.selectedEntity); - const leftView = useUIStore((state) => state.leftNavigationView); - const previewBuilding = useUIStore((state) => state.previewBuilding); - - const setQuests = useQuestStore((state) => state.setQuests); - const selectedQuest = useQuestStore((state) => state.selectedQuest); - const setSelectedQuest = useQuestStore((state) => state.setSelectedQuest); - const setClaimableQuestsLength = useQuestStore((state) => state.setClaimableQuestsLength); - - const { playerRealms } = useEntities(); - const realm = playerRealms()[0]; - const entityId = realm?.entity_id; - const realmPosition = realm?.position; - - const [location, _] = useLocation(); - const isWorldView = useMemo(() => location === "/map", [location]); - - const getBuildingQuantity = (buildingType: BuildingType) => - getComponentValue(BuildingQuantityv2, getEntityIdFromKeys([BigInt(entityId || "0"), BigInt(buildingType)])) - ?.value || 0; - - const farms = getBuildingQuantity(BuildingType.Farm); - const resource = getBuildingQuantity(BuildingType.Resource); - const workersHut = getBuildingQuantity(BuildingType.WorkersHut); - const markets = getBuildingQuantity(BuildingType.Market); - - const { playerStructures } = useEntities(); - const structures = playerStructures(); - - const countStructuresByCategory = useCallback( - (category: string) => { - return structures.filter((structure) => structure.category === category).length; - }, - [structures], - ); - - const fragmentMines = useMemo( - () => countStructuresByCategory(StructureType[StructureType.FragmentMine]), - [countStructuresByCategory], - ); - const hyperstructures = useMemo( - () => countStructuresByCategory(StructureType[StructureType.Hyperstructure]), - [countStructuresByCategory], - ); - - const hyperstructureContributions = runQuery([ - HasValue(Contribution, { player_address: BigInt(account.address) }), - ]).size; - - const orders = useGetMyOffers(); - - const { entityArmies } = useArmiesByEntityOwner({ entity_owner_entity_id: entityId || BigInt("0") }); - - const [pillageHistoryLength, setPillageHistoryLength] = useState(0); - - const hasTroops = useMemo(() => armyHasTroops(entityArmies), [entityArmies]); - const hasTraveled = useMemo(() => armyHasTraveled(entityArmies, realmPosition), [entityArmies, realmPosition]); - - useEffect(() => { - const fetchPillageHistory = async () => { - const eventsLength = await getPillageEvents(BigInt(entityId ?? "0")); - setPillageHistoryLength(eventsLength); - }; - fetchPillageHistory(); - }, [entityId]); - - const quests: Quest[] = useMemo(() => { - const updatedQuests = [ - { - name: QuestName.Settle, - description: "A gift of food from the gods.", - completed: true, - steps: [{ description: "Settle your first Realm.", completed: true }], - prizes: [{ id: QuestType.Food, title: "Common Resources" }], - depth: 0, - }, - { - name: QuestName.BuildFarm, - description: "Wheat is the lifeblood of your people. Go to the construction menu and build a farm.", - completed: farms > 0, - steps: [ - { description: "Navigate to the construction menu.", completed: leftView === LeftView.ConstructionView }, - { - description: "Select the 'Farm' card", - completed: previewBuilding !== null, - }, - { - description: "Left click on a hex to build it, or right click to cancel.", - completed: farms > 0, - }, - ], - prizes: [ - { id: QuestType.CommonResources, title: "Common Resources" }, - { id: QuestType.UncommonResources, title: "Uncommon Resources" }, - { id: QuestType.RareResources, title: "Rare Resources" }, - { id: QuestType.UniqueResources, title: "Unique Resources" }, - { id: QuestType.LegendaryResources, title: "Legendary Resources" }, - { id: QuestType.MythicResources, title: "Mythic Resources" }, - ], - depth: 1, - }, - { - name: QuestName.BuildResource, - description: "Eternum thrives on resources. Construct resource facilities to harvest them efficiently.", - completed: resource > 0, - steps: [ - { description: "Navigate to the construction menu.", completed: leftView === LeftView.ConstructionView }, - { - description: "Navigate to the 'Resources' tab and select a resource card", - completed: previewBuilding !== null, - }, - { - description: "Left click on a hex to build it, or right click to cancel.", - completed: resource > 0, - }, - ], - prizes: [{ id: QuestType.Trade, title: "Donkeys and Lords" }], - depth: 2, - }, - { - name: QuestName.CreateTrade, - description: "Trading is the lifeblood of Eternum. Create a trade to start your economy.", - completed: orders.length > 0, - steps: [], - prizes: [{ id: QuestType.Military, title: "Claim Starting Army" }], - depth: 3, - }, - { - name: QuestName.CreateArmy, - description: "Conquest is fulfilling. Create an army to conquer your enemies.", - completed: entityArmies.length > 0 && hasTroops, - steps: [ - { description: "Create an army to conquer your enemies.", completed: entityArmies.length > 0 }, - { - description: "Assign troops to your army", - completed: hasTroops, - }, - ], - prizes: [{ id: QuestType.Earthenshard, title: "Claim Earthen Shard" }], - depth: 3, - }, - { - name: QuestName.Travel, - description: "Travel with your army.", - completed: armyHasTraveled(entityArmies, realmPosition), - steps: [ - { description: "Go to world view.", completed: isWorldView }, - { - description: "Right click on your army", - completed: selectedEntity != null || hasTraveled, - }, - { description: "Travel w/ your army.", completed: hasTraveled }, - ], - prizes: [{ id: QuestType.Travel, title: "Travel" }], - depth: 4, - }, - { - name: QuestName.BuildWorkersHut, - description: "Build worker huts to extend your population capacity.", - completed: workersHut > 0, - steps: [], - prizes: [{ id: QuestType.Population, title: "Population" }], - depth: 5, - }, - { - name: QuestName.Market, - description: "Build a market to produce donkeys. Donkeys are a resource used to transport goods.", - completed: markets > 0, - steps: [], - prizes: [{ id: QuestType.Market, title: "Market" }], - depth: 5, - }, - { - name: QuestName.Pillage, - description: "Pillage a realm, hyperstructure or earthenshard mine.", - completed: pillageHistoryLength > 0, - steps: [], - prizes: [{ id: QuestType.Pillage, title: "Pillage" }], - depth: 5, - }, - { - name: QuestName.Mine, - description: "Explore the world, find earthenshard mines.", - completed: fragmentMines > 0, - steps: [], - prizes: [{ id: QuestType.Mine, title: "Mine" }], - depth: 5, - }, - { - name: QuestName.Contribution, - description: "Contribute to a Hyperstructure.", - completed: hyperstructureContributions > 0, - steps: [], - prizes: [{ id: QuestType.Contribution, title: "Contribution" }], - depth: 5, - }, - { - name: QuestName.Hyperstructure, - description: "Build a Hyperstructure.", - completed: hyperstructures > 0, - steps: [], - prizes: [{ id: QuestType.Hyperstructure, title: "Hyperstructure" }], - depth: 5, - }, - ]; - - return updatedQuests.map((quest) => { - const claimed = quest.prizes.every((prize) => { - const value = getComponentValue( - HasClaimedStartingResources, - getEntityIdFromKeys([BigInt(entityId || "0"), BigInt(prize.id)]), - ); - return value?.claimed; - }); - return { ...quest, claimed }; - }); - }, [ - farms, - resource, - orders, - entityArmies, - hasTroops, - hasTraveled, - workersHut, - markets, - fragmentMines, - hyperstructures, - hyperstructureContributions, - pillageHistoryLength, - selectedEntity, - isWorldView, - leftView, - previewBuilding, - ]); - - useEffect(() => { - setQuests(quests); - - const claimableQuestsLenght = quests.filter((quest) => !quest.claimed).length; - setClaimableQuestsLength(claimableQuestsLenght); - }, []); - - useEffect(() => { - setQuests(quests); - setSelectedQuest(quests.find((quest) => quest.name === selectedQuest?.name)); - - const claimableQuestsLenght = quests.filter((quest) => !quest.claimed).length; - setClaimableQuestsLength(claimableQuestsLenght); - }, [quests]); -}; - -const armyHasTroops = (entityArmies: ArmyInfo[]) => { - return ( - entityArmies && - entityArmies[0] && - (Number(entityArmies[0].troops.knight_count) != 0 || - Number(entityArmies[0].troops.crossbowman_count) != 0 || - Number(entityArmies[0].troops.paladin_count) != 0) - ); -}; - -const armyHasTraveled = (entityArmies: ArmyInfo[], realmPosition: { x: number; y: number }) => { - if (entityArmies && entityArmies[0] && realmPosition) { - return entityArmies[0].position.x != realmPosition.x || entityArmies[0].position.y != realmPosition.y; - } - return false; -}; +export const useQuestStore = create((set) => ({ + selectedQuest: null, + setSelectedQuest: (selectedQuest: Quest | null) => set({ selectedQuest }), +})); diff --git a/client/src/ui/components/HooksComponent.tsx b/client/src/ui/components/HooksComponent.tsx index 923a963cc..c39c8b400 100644 --- a/client/src/ui/components/HooksComponent.tsx +++ b/client/src/ui/components/HooksComponent.tsx @@ -6,14 +6,12 @@ import { Hexagon } from "@/types"; import { useSetExistingStructures } from "@/hooks/store/_mapStore"; import { useComputePointsLeaderboards } from "@/hooks/store/useLeaderBoardStore"; import { useTravelPath } from "./worldmap/hexagon/useTravelPath"; -import { useQuests } from "@/hooks/store/useQuestStore"; export const HooksComponent = () => { useFetchBlockchainData(); useSetExistingStructures(); useComputePointsLeaderboards(); useTravelPath(); - useQuests(); const setHexData = useUIStore((state) => state.setHexData); diff --git a/client/src/ui/components/construction/SelectPreviewBuilding.tsx b/client/src/ui/components/construction/SelectPreviewBuilding.tsx index c4dfe284f..8262f21c0 100644 --- a/client/src/ui/components/construction/SelectPreviewBuilding.tsx +++ b/client/src/ui/components/construction/SelectPreviewBuilding.tsx @@ -18,7 +18,7 @@ import { import { ReactComponent as InfoIcon } from "@/assets/icons/common/info.svg"; import { useGetRealm } from "@/hooks/helpers/useRealm"; import { useResourceBalance } from "@/hooks/helpers/useResources"; -import { QuestName, useQuestStore } from "@/hooks/store/useQuestStore"; +import { useQuestStore } from "@/hooks/store/useQuestStore"; import useRealmStore from "@/hooks/store/useRealmStore"; import { usePlayResourceSound } from "@/hooks/useUISound"; import { Headline } from "@/ui/elements/Headline"; @@ -32,21 +32,21 @@ import { BUILDING_COSTS_SCALED } from "@bibliothecadao/eternum"; import React, { useMemo, useState } from "react"; import { BUILDING_IMAGES_PATH } from "@/ui/config"; import { HintSection } from "../hints/HintModal"; +import { useQuestClaimStatus } from "@/hooks/helpers/useQuests"; +import { QuestId } from "@/ui/components/quest/questDetails"; // TODO: THIS IS TERRIBLE CODE, PLEASE REFACTOR export const SelectPreviewBuildingMenu = () => { const setPreviewBuilding = useUIStore((state) => state.setPreviewBuilding); const previewBuilding = useUIStore((state) => state.previewBuilding); - const realmEntityId = useRealmStore((state) => state.realmEntityId); - const quests = useQuestStore((state) => state.quests); const selectedQuest = useQuestStore((state) => state.selectedQuest); const { realm } = useGetRealm(realmEntityId); - const { getBalance } = useResourceBalance(); const { playResourceSound } = usePlayResourceSound(); + const { questClaimStatus } = useQuestClaimStatus(); const buildingTypes = Object.keys(BuildingType).filter( (key) => @@ -80,11 +80,6 @@ export const SelectPreviewBuildingMenu = () => { const [selectedTab, setSelectedTab] = useState(1); - const isQuestClaimedByName = (questName: QuestName) => { - const quest = quests?.find((q) => q.name === questName); - return quest?.claimed ?? false; - }; - const tabs = useMemo( () => [ { @@ -93,8 +88,7 @@ export const SelectPreviewBuildingMenu = () => {
Resources @@ -118,7 +112,7 @@ export const SelectPreviewBuildingMenu = () => { return ( { return ( { return ( { const { entityArmies: structureArmies } = useArmiesByEntityOwner({ @@ -62,7 +63,7 @@ export const EntityArmyList = ({ structure }: { structure: PlayerStructures }) = onClick={() => handleCreateArmy(false)} disabled={isLoading} className={clsx({ - "animate-pulse": selectedQuest?.name === QuestName.CreateArmy && !selectedQuest.steps[0].completed, + "animate-pulse": selectedQuest?.id === QuestId.CreateArmy && !structureArmies.length, })} > Create Army @@ -90,11 +91,7 @@ export const EntityArmyList = ({ structure }: { structure: PlayerStructures }) = /> )} - questing={ - selectedQuest?.name === QuestName.CreateArmy && - selectedQuest.steps[0].completed && - !selectedQuest.steps[1].completed - } + questing={selectedQuest?.id === QuestId.CreateArmy} /> ); diff --git a/client/src/ui/components/quest/QuestInfo.tsx b/client/src/ui/components/quest/QuestInfo.tsx index ec2a85ec9..bc6e2ca14 100644 --- a/client/src/ui/components/quest/QuestInfo.tsx +++ b/client/src/ui/components/quest/QuestInfo.tsx @@ -1,11 +1,12 @@ import { useDojo } from "@/hooks/context/DojoContext"; import { useRealm } from "@/hooks/helpers/useRealm"; -import { Prize, Quest, useQuestStore } from "@/hooks/store/useQuestStore"; +import { useQuestStore } from "@/hooks/store/useQuestStore"; import Button from "@/ui/elements/Button"; import { useState } from "react"; import { Check, ShieldQuestion } from "lucide-react"; import { multiplyByPrecision } from "@/ui/utils/utils"; import { ResourceCost } from "@/ui/elements/ResourceCost"; +import { Prize, Quest, QuestStatus } from "@/hooks/helpers/useQuests"; export const QuestInfo = ({ quest, entityId }: { quest: Quest; entityId: bigint }) => { const { @@ -42,36 +43,42 @@ export const QuestInfo = ({ quest, entityId }: { quest: Quest; entityId: bigint return ( <> - -
+
{quest.name}
- {quest.completed ? : } + {quest.status !== QuestStatus.InProgress ? : }

{quest.description}

- {quest.steps?.map(({ description, completed }, index) => ( + {quest.steps?.map((step: any, index: any) => (
-
- {description}
- {completed ? : } +
- {step}
))}
- {quest.completed && - (quest.claimed ? ( -
Rewards claimed
- ) : ( + {quest.status !== QuestStatus.Claimed ? ( + quest.status === QuestStatus.Completed && ( - ))} + ) + ) : ( +
Rewards claimed
+ )}
diff --git a/client/src/ui/components/quest/QuestList.tsx b/client/src/ui/components/quest/QuestList.tsx index 6c701377f..26815435d 100644 --- a/client/src/ui/components/quest/QuestList.tsx +++ b/client/src/ui/components/quest/QuestList.tsx @@ -1,16 +1,16 @@ -import { Prize, Quest, useQuestStore } from "@/hooks/store/useQuestStore"; +import { useQuestStore } from "@/hooks/store/useQuestStore"; import { useEffect, useMemo, useState } from "react"; import { areAllQuestsClaimed, groupQuestsByDepth } from "./utils"; import Button from "@/ui/elements/Button"; import { useDojo } from "@/hooks/context/DojoContext"; import { useRealm } from "@/hooks/helpers/useRealm"; import { multiplyByPrecision } from "@/ui/utils/utils"; +import { Prize, Quest, QuestStatus, useQuests } from "@/hooks/helpers/useQuests"; export const QuestList = ({ quests, entityId }: { quests: Quest[]; entityId: bigint | undefined }) => { - const showCompletedQuests = useQuestStore((state) => state.showCompletedQuests); - const setShowCompletedQuests = useQuestStore((state) => state.setShowCompletedQuests); + const [showCompletedQuests, setShowCompletedQuests] = useState(false); const [skipTutorial, setSkipTutorial] = useState(false); - const [maxDepthToShow, setMaxDepthToShow] = useState(1); + const [maxDepthToShow, setMaxDepthToShow] = useState(0); const groupedQuests = useMemo(() => groupQuestsByDepth(quests), [quests]); @@ -22,8 +22,7 @@ export const QuestList = ({ quests, entityId }: { quests: Quest[]; entityId: big return depth + 1; } return max; - // need to start from depth 1 - }, 1); + }, 0); setMaxDepthToShow(newMaxDepth); }, [quests, groupedQuests]); @@ -51,9 +50,11 @@ export const QuestList = ({ quests, entityId }: { quests: Quest[]; entityId: big .sort(([a], [b]) => Number(a) - Number(b)) .map(([depth, depthQuests]) => { if (Number(depth) > maxDepthToShow) return null; - const uncompletedQuests = depthQuests.filter((quest) => !quest.claimed || showCompletedQuests); - if (uncompletedQuests.length === 0) return null; - return ; + const shownQuests = depthQuests.filter( + (quest) => quest.status !== QuestStatus.Claimed || showCompletedQuests, + ); + if (shownQuests.length === 0) return null; + return ; })}
@@ -73,7 +74,7 @@ const QuestCard = ({ quest }: { quest: Quest }) => { return (
setSelectedQuest(quest)} >
{quest.name}
@@ -90,12 +91,12 @@ const SkipTutorial = ({ entityId }: { entityId: bigint }) => { } = useDojo(); const [isLoading, setIsLoading] = useState(false); - const quests = useQuestStore((state) => state.quests); + const { quests } = useQuests(); const { getQuestResources } = useRealm(); const questResources = getQuestResources(); - const unclaimedQuests = quests?.filter((quest) => !quest.claimed); + const unclaimedQuests = quests?.filter((quest) => quest.status === QuestStatus.InProgress); const resourcesToMint = unclaimedQuests?.flatMap((quest: Quest) => diff --git a/client/src/ui/components/quest/QuestPanel.tsx b/client/src/ui/components/quest/QuestPanel.tsx index f614e5b4e..c53442b54 100644 --- a/client/src/ui/components/quest/QuestPanel.tsx +++ b/client/src/ui/components/quest/QuestPanel.tsx @@ -1,18 +1,22 @@ import { useQuestStore } from "@/hooks/store/useQuestStore"; import { QuestInfo } from "./QuestInfo"; import { QuestList } from "./QuestList"; +import { useQuests } from "@/hooks/helpers/useQuests"; +import { useMemo } from "react"; export const QuestPanel = ({ entityId }: { entityId: bigint | undefined }) => { - const { quests, selectedQuest } = useQuestStore((state) => ({ - quests: state.quests, + const { selectedQuest } = useQuestStore((state) => ({ selectedQuest: state.selectedQuest, })); + const { quests } = useQuests(); + const updatedSelectedQuest = quests.find((quest) => quest.id === selectedQuest?.id); + return selectedQuest ? (
- +
) : ( - + ); }; diff --git a/client/src/ui/components/quest/questDetails.tsx b/client/src/ui/components/quest/questDetails.tsx new file mode 100644 index 000000000..9988c8dde --- /dev/null +++ b/client/src/ui/components/quest/questDetails.tsx @@ -0,0 +1,163 @@ +import { Prize } from "@/hooks/helpers/useQuests"; +import { QuestType } from "@bibliothecadao/eternum"; + +interface StaticQuestInfo { + name: string; + description: string; + steps: string[]; + prizes: Prize[]; + depth: number; +} + +export enum QuestId { + Settle, + BuildFarm, + BuildResource, + CreateTrade, + CreateArmy, + Travel, + BuildWorkersHut, + Market, + Pillage, + Mine, + Contribution, + Hyperstructure, +} + +export const questDetails = new Map([ + [ + QuestId.Settle, + { + name: "Settle", + description: "A gift of food from the gods", + steps: ["Settle your first Realm"], + prizes: [{ id: QuestType.Food, title: "Common Resources" }], + depth: 0, + }, + ], + [ + QuestId.BuildFarm, + { + name: "Build a Farm", + description: "Wheat is the lifeblood of your people. Go to the construction menu and build a farm", + steps: [ + "Navigate to the construction menu", + "Select the 'Farm' card", + "Left click on a hex to build it, or right click to cancel", + ], + prizes: [ + { id: QuestType.CommonResources, title: "Common Resources" }, + { id: QuestType.UncommonResources, title: "Uncommon Resources" }, + { id: QuestType.RareResources, title: "Rare Resources" }, + { id: QuestType.UniqueResources, title: "Unique Resources" }, + { id: QuestType.LegendaryResources, title: "Legendary Resources" }, + { id: QuestType.MythicResources, title: "Mythic Resources" }, + ], + depth: 1, + }, + ], + [ + QuestId.BuildResource, + { + name: "Build a Resource Facility", + description: "Eternum thrives on resources. Construct resource facilities to harvest them efficiently", + steps: [ + "Navigate to the construction menu", + "Navigate to the 'Resources' tab and select a resource card", + "Left click on a hex to build it, or right click to cancel", + ], + prizes: [{ id: QuestType.Trade, title: "Donkeys and Lords" }], + depth: 2, + }, + ], + [ + QuestId.CreateTrade, + { + name: "Create a Trade", + description: "Trading is the lifeblood of Eternum. Create a trade to start your economy", + steps: [], + prizes: [{ id: QuestType.Military, title: "Claim Starting Army" }], + depth: 3, + }, + ], + [ + QuestId.CreateArmy, + { + name: "Create an Army", + description: "Conquest is fulfilling. Create an army to conquer your enemies", + steps: ["Create an army to conquer your enemies", "Assign troops to your army"], + prizes: [{ id: QuestType.Earthenshard, title: "Claim Earthen Shard" }], + depth: 4, + }, + ], + [ + QuestId.Travel, + { + name: "Travel with your Army", + description: "Travel with your army", + steps: ["Go to world view", "Right click on your army", "Travel w/ your army"], + prizes: [{ id: QuestType.Travel, title: "Travel" }], + depth: 5, + }, + ], + [ + QuestId.BuildWorkersHut, + { + name: "Build a workers hut", + description: "Build worker huts to extend your population capacity", + steps: [], + prizes: [{ id: QuestType.Population, title: "Population" }], + depth: 6, + }, + ], + [ + QuestId.Market, + { + name: "Build a market", + description: "Build a market to produce donkeys. Donkeys are a resource used to transport goods", + steps: [], + prizes: [{ id: QuestType.Market, title: "Market" }], + depth: 6, + }, + ], + [ + QuestId.Pillage, + { + name: "Pillage a structure", + description: "Pillage a realm, hyperstructure or earthenshard mine", + steps: [], + prizes: [{ id: QuestType.Pillage, title: "Pillage" }], + depth: 6, + }, + ], + [ + QuestId.Mine, + { + name: "Discover an earthenshard mine", + description: "Explore the world, find earthenshard mines", + steps: [], + prizes: [{ id: QuestType.Mine, title: "Mine" }], + depth: 6, + }, + ], + [ + QuestId.Contribution, + { + name: "Contribute to a hyperstructure", + description: "Contribute to a Hyperstructure", + steps: [], + prizes: [{ id: QuestType.Contribution, title: "Contribution" }], + depth: 6, + }, + ], + [ + QuestId.Hyperstructure, + { + name: "Build a hyperstructure", + description: "Build a Hyperstructure", + steps: [], + prizes: [{ id: QuestType.Hyperstructure, title: "Hyperstructure" }], + depth: 6, + }, + ], +]); diff --git a/client/src/ui/components/quest/utils.tsx b/client/src/ui/components/quest/utils.tsx index c85a399ce..4cc4afcdd 100644 --- a/client/src/ui/components/quest/utils.tsx +++ b/client/src/ui/components/quest/utils.tsx @@ -1,4 +1,4 @@ -import { Quest } from "@/hooks/store/useQuestStore"; +import { Quest, QuestStatus } from "@/hooks/helpers/useQuests"; export const groupQuestsByDepth = (quests: Quest[]): Record => { return quests?.reduce((groupedQuests: Record, quest) => { @@ -11,4 +11,4 @@ export const groupQuestsByDepth = (quests: Quest[]): Record => }, {}); }; -export const areAllQuestsClaimed = (quests: Quest[]) => quests.every((quest) => quest.claimed); +export const areAllQuestsClaimed = (quests: Quest[]) => quests.every((quest) => quest.status === QuestStatus.Claimed); diff --git a/client/src/ui/components/structures/construction/StructureConstructionMenu.tsx b/client/src/ui/components/structures/construction/StructureConstructionMenu.tsx index 6cc185800..8c6d7ffba 100644 --- a/client/src/ui/components/structures/construction/StructureConstructionMenu.tsx +++ b/client/src/ui/components/structures/construction/StructureConstructionMenu.tsx @@ -20,8 +20,9 @@ import { BUILDING_COSTS_SCALED } from "@bibliothecadao/eternum"; import { useResourceBalance } from "@/hooks/helpers/useResources"; import { Headline } from "@/ui/elements/Headline"; import { StructureCard } from "./StructureCard"; -import { QuestName, useQuestStore } from "@/hooks/store/useQuestStore"; +import { useQuestStore } from "@/hooks/store/useQuestStore"; import clsx from "clsx"; +import { QuestId } from "@/ui/components/quest/questDetails"; const STRUCTURE_IMAGE_PREFIX = "/images/buildings/thumb/"; export const STRUCTURE_IMAGE_PATHS = { @@ -66,7 +67,7 @@ export const StructureConstructionMenu = () => { return ( { diff --git a/client/src/ui/modules/navigation/BottomNavigation.tsx b/client/src/ui/modules/navigation/BottomNavigation.tsx index 369227ebb..91cef5914 100644 --- a/client/src/ui/modules/navigation/BottomNavigation.tsx +++ b/client/src/ui/modules/navigation/BottomNavigation.tsx @@ -1,5 +1,5 @@ import { useEntities } from "@/hooks/helpers/useEntities"; -import { QuestName, useQuestStore } from "@/hooks/store/useQuestStore"; +import { useQuestStore } from "@/hooks/store/useQuestStore"; import useRealmStore from "@/hooks/store/useRealmStore"; import useUIStore from "@/hooks/store/useUIStore"; import CircleButton from "@/ui/elements/CircleButton"; @@ -11,6 +11,8 @@ import { useMemo } from "react"; import { useLocation } from "wouter"; import { guilds, leaderboard, quests as questsWindow } from "../../components/navigation/Config"; import { BuildingThumbs } from "./LeftNavigationModule"; +import { QuestStatus, useUnclaimedQuestsCount, useQuests, useQuestClaimStatus } from "@/hooks/helpers/useQuests"; +import { QuestId } from "@/ui/components/quest/questDetails"; export enum MenuEnum { realm = "realm", @@ -27,22 +29,23 @@ export enum MenuEnum { } export const BottomNavigation = () => { - const [location, setLocation] = useLocation(); + const [location, _] = useLocation(); const { realmEntityId } = useRealmStore(); + const { quests } = useQuests(); + const { unclaimedQuestsCount } = useUnclaimedQuestsCount(); + const { questClaimStatus } = useQuestClaimStatus(); + const togglePopup = useUIStore((state) => state.togglePopup); const isPopupOpen = useUIStore((state) => state.isPopupOpen); + const selectedQuest = useQuestStore((state) => state.selectedQuest); const isWorldView = useMemo(() => location === "/map", [location]); - const quests = useQuestStore((state) => state.quests); - const selectedQuest = useQuestStore((state) => state.selectedQuest); - const claimableQuestsLength = useQuestStore((state) => state.claimableQuestsLength); - const { playerStructures } = useEntities(); const structures = useMemo(() => playerStructures(), [playerStructures]); - const questToClaim = quests?.find((quest) => quest.completed && !quest.claimed); + const questToClaim = quests?.find((quest: any) => quest.status === QuestStatus.Completed); const secondaryNavigation = useMemo(() => { return [ @@ -56,7 +59,7 @@ export const BottomNavigation = () => { active={isPopupOpen(questsWindow)} size="lg" onClick={() => togglePopup(questsWindow)} - notification={isRealmSelected(realmEntityId, structures) ? claimableQuestsLength : undefined} + notification={isRealmSelected(realmEntityId, structures) ? unclaimedQuestsCount : undefined} notificationLocation={"topleft"} disabled={!isRealmSelected(realmEntityId, structures)} /> @@ -79,7 +82,7 @@ export const BottomNavigation = () => { active={isPopupOpen(leaderboard)} size="lg" onClick={() => togglePopup(leaderboard)} - className={clsx({ hidden: !quests?.find((quest) => quest.name === QuestName.Travel)?.claimed })} + className={clsx({ hidden: !questClaimStatus[QuestId.Travel] })} /> ), }, @@ -92,12 +95,14 @@ export const BottomNavigation = () => { active={isPopupOpen(guilds)} size="lg" onClick={() => togglePopup(guilds)} - className={clsx({ hidden: !quests?.find((quest) => quest.name === QuestName.Travel)?.claimed })} + className={clsx({ + hidden: !questClaimStatus[QuestId.Travel], + })} /> ), }, ]; - }, [claimableQuestsLength, selectedQuest, quests]); + }, [unclaimedQuestsCount, selectedQuest, quests, realmEntityId]); const slideUp = { hidden: { y: "100%", transition: { duration: 0.3 } }, diff --git a/client/src/ui/modules/navigation/LeftNavigationModule.tsx b/client/src/ui/modules/navigation/LeftNavigationModule.tsx index a00ed95a7..54e45945e 100644 --- a/client/src/ui/modules/navigation/LeftNavigationModule.tsx +++ b/client/src/ui/modules/navigation/LeftNavigationModule.tsx @@ -2,7 +2,7 @@ import { useArmiesByEntityOwner } from "@/hooks/helpers/useArmies"; import { useEntitiesUtils } from "@/hooks/helpers/useEntities"; import { useStamina } from "@/hooks/helpers/useStamina"; import useBlockchainStore from "@/hooks/store/useBlockchainStore"; -import { QuestName, useQuestStore } from "@/hooks/store/useQuestStore"; +import { useQuestStore } from "@/hooks/store/useQuestStore"; import useUIStore from "@/hooks/store/useUIStore"; import { SelectPreviewBuildingMenu } from "@/ui/components/construction/SelectPreviewBuilding"; import { StructureConstructionMenu } from "@/ui/components/structures/construction/StructureConstructionMenu"; @@ -26,6 +26,8 @@ import { Leaderboard } from "../leaderboard/LeaderBoard"; import { Questing } from "../questing/Questing"; import { WorldStructuresMenu } from "../world-structures/WorldStructuresMenu"; import { MenuEnum } from "./BottomNavigation"; +import { useQuestClaimStatus } from "@/hooks/helpers/useQuests"; +import { QuestId } from "@/ui/components/quest/questDetails"; export const BuildingThumbs = { hex: "/images/buildings/thumb/question.png", @@ -56,38 +58,33 @@ export enum View { export const LeftNavigationModule = () => { const [lastView, setLastView] = useState(View.None); - const currentArmiesTick = useBlockchainStore((state) => state.currentArmiesTick); const view = useUIStore((state) => state.leftNavigationView); const setView = useUIStore((state) => state.setLeftNavigationView); const previewBuilding = useUIStore((state) => state.previewBuilding); const isPopupOpen = useUIStore((state) => state.isPopupOpen); const openedPopups = useUIStore((state) => state.openedPopups); - const quests = useQuestStore((state) => state.quests); const selectedQuest = useQuestStore((state) => state.selectedQuest); const { realmEntityId } = useRealmStore(); - const { getStamina } = useStamina(); + const { entityArmies } = useArmiesByEntityOwner({ entity_owner_entity_id: realmEntityId }); + const { useArmiesCanMoveCount } = useStamina(); + const armiesCanMoveCount = useArmiesCanMoveCount(entityArmies); + + const { questClaimStatus } = useQuestClaimStatus(); const [location, _] = useLocation(); const isWorldView = useMemo(() => location === "/map", [location]); - const armiesWithStaminaLeft = entityArmies?.filter((entity) => { - return ( - getStamina({ travelingEntityId: BigInt(entity.entity_id), currentArmiesTick })?.amount || - 0 >= EternumGlobalConfig.stamina.travelCost - ); - }); - const isBuildQuest = useMemo(() => { return ( - selectedQuest?.name === QuestName.BuildFarm || - selectedQuest?.name === QuestName.BuildResource || - selectedQuest?.name === QuestName.BuildWorkersHut || - selectedQuest?.name === QuestName.Market || - (selectedQuest?.name === QuestName.Hyperstructure && isWorldView) + selectedQuest?.id === QuestId.BuildFarm || + selectedQuest?.id === QuestId.BuildResource || + selectedQuest?.id === QuestId.BuildWorkersHut || + selectedQuest?.id === QuestId.Market || + (selectedQuest?.id === QuestId.Hyperstructure && isWorldView) ); }, [selectedQuest, isWorldView]); const { getEntityInfo } = useEntitiesUtils(); @@ -99,7 +96,7 @@ export const LeftNavigationModule = () => { name: "entityDetails", button: ( quest.name === QuestName.CreateArmy)?.claimed })} + className={clsx({ hidden: !questClaimStatus[QuestId.CreateArmy] })} image={BuildingThumbs.hex} tooltipLocation="top" label={"Details"} @@ -118,10 +115,8 @@ export const LeftNavigationModule = () => { quest.name === QuestName.BuildResource)?.claimed, + view != View.ConstructionView && selectedQuest?.id === QuestId.CreateArmy && isPopupOpen(questsPopup), + hidden: !questClaimStatus[QuestId.CreateTrade], })} image={BuildingThumbs.military} tooltipLocation="top" @@ -132,7 +127,7 @@ export const LeftNavigationModule = () => { setLastView(View.MilitaryView); setView(View.MilitaryView); }} - notification={armiesWithStaminaLeft.length} + notification={armiesCanMoveCount} notificationLocation="topright" /> ), @@ -162,11 +157,9 @@ export const LeftNavigationModule = () => { button: ( quest.name === QuestName.CreateArmy)?.claimed, + hidden: !questClaimStatus[QuestId.CreateArmy], "animate-pulse": - view != View.ConstructionView && - selectedQuest?.name === QuestName.Contribution && - isPopupOpen(questsPopup), + view != View.ConstructionView && selectedQuest?.id === QuestId.Contribution && isPopupOpen(questsPopup), })} image={BuildingThumbs.worldStructures} tooltipLocation="top" @@ -197,7 +190,7 @@ export const LeftNavigationModule = () => { item.name === MenuEnum.construction || item.name === MenuEnum.worldStructures, ); - }, [location, view, armiesWithStaminaLeft, quests, openedPopups]); + }, [location, view, openedPopups, selectedQuest, armiesCanMoveCount]); if (realmEntityId === undefined) { return null; diff --git a/client/src/ui/modules/navigation/RightNavigationModule.tsx b/client/src/ui/modules/navigation/RightNavigationModule.tsx index 75e901fa7..e40199f3a 100644 --- a/client/src/ui/modules/navigation/RightNavigationModule.tsx +++ b/client/src/ui/modules/navigation/RightNavigationModule.tsx @@ -12,7 +12,7 @@ import { useMemo, useState } from "react"; import { BaseContainer } from "../../containers/BaseContainer"; import { useEntities, useEntitiesUtils } from "@/hooks/helpers/useEntities"; -import { QuestName, useQuestStore } from "@/hooks/store/useQuestStore"; +import { useQuestStore } from "@/hooks/store/useQuestStore"; import { HintSection } from "@/ui/components/hints/HintModal"; import { Headline } from "@/ui/elements/Headline"; import { HintModalButton } from "@/ui/elements/HintModalButton"; @@ -21,6 +21,8 @@ import { motion } from "framer-motion"; import { ArrowRight } from "lucide-react"; import { quests as questsPopup } from "../../components/navigation/Config"; import { BuildingThumbs } from "./LeftNavigationModule"; +import { QuestStatus, useQuestClaimStatus } from "@/hooks/helpers/useQuests"; +import { QuestId } from "@/ui/components/quest/questDetails"; export enum View { None, @@ -37,14 +39,14 @@ export const RightNavigationModule = () => { const isPopupOpen = useUIStore((state) => state.isPopupOpen); const openedPopups = useUIStore((state) => state.openedPopups); + const selectedQuest = useQuestStore((state) => state.selectedQuest); + const { realmEntityId } = useRealmStore(); + const { questClaimStatus } = useQuestClaimStatus(); const { getEntityInfo } = useEntitiesUtils(); const realmIsMine = getEntityInfo(realmEntityId).isMine; - const quests = useQuestStore((state) => state.quests); - const selectedQuest = useQuestStore((state) => state.selectedQuest); - const { getAllArrivalsWithResources } = useResources(); const { toggleModal } = useModal(); @@ -72,7 +74,7 @@ export const RightNavigationModule = () => { button: ( quest.name === QuestName.CreateTrade)?.claimed })} + className={clsx({ hidden: !questClaimStatus[QuestId.CreateTrade] })} image={BuildingThumbs.trade} tooltipLocation="top" label={"Resource Arrivals"} @@ -95,8 +97,10 @@ export const RightNavigationModule = () => { disabled={!realmIsMine} className={clsx({ "animate-pulse": - selectedQuest?.name === QuestName.CreateTrade && !selectedQuest.completed && isPopupOpen(questsPopup), - hidden: !quests?.find((quest) => quest.name === QuestName.BuildResource)?.claimed, + selectedQuest?.id === QuestId.CreateTrade && + selectedQuest.status !== QuestStatus.Completed && + isPopupOpen(questsPopup), + hidden: !questClaimStatus[QuestId.BuildResource], })} image={BuildingThumbs.scale} tooltipLocation="top" @@ -110,7 +114,7 @@ export const RightNavigationModule = () => { ), }, ]; - }, [location, view, quests, openedPopups, selectedQuest, getAllArrivalsWithResources]); + }, [location, view, questClaimStatus, openedPopups, selectedQuest, getAllArrivalsWithResources]); const slideRight = { hidden: { x: "100%" }, diff --git a/client/src/ui/modules/navigation/TopMiddleNavigation.tsx b/client/src/ui/modules/navigation/TopMiddleNavigation.tsx index a7f702eae..4a4a9ed1d 100644 --- a/client/src/ui/modules/navigation/TopMiddleNavigation.tsx +++ b/client/src/ui/modules/navigation/TopMiddleNavigation.tsx @@ -19,9 +19,11 @@ import { useMemo } from "react"; import { useLocation } from "wouter"; import useBlockchainStore from "../../../hooks/store/useBlockchainStore"; -import { QuestName, useQuestStore } from "@/hooks/store/useQuestStore"; +import { useQuestStore } from "@/hooks/store/useQuestStore"; import { useComponentValue } from "@dojoengine/react"; import clsx from "clsx"; +import { QuestStatus } from "@/hooks/helpers/useQuests"; +import { QuestId } from "@/ui/components/quest/questDetails"; import { motion } from "framer-motion"; const slideDown = { @@ -135,8 +137,8 @@ export const TopMiddleNavigation = () => { variant="primary" className={clsx({ "animate-pulse": - (selectedQuest?.name === QuestName.Travel || selectedQuest?.name === QuestName.Hyperstructure) && - !selectedQuest.completed && + (selectedQuest?.id === QuestId.Travel || selectedQuest?.id === QuestId.Hyperstructure) && + selectedQuest.status !== QuestStatus.Completed && isHexView, })} onClick={() => { diff --git a/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx b/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx index 0d6707988..1013d8fc0 100644 --- a/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx +++ b/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx @@ -13,16 +13,17 @@ import { useShardMines } from "@/hooks/helpers/useShardMines"; import { useContributions } from "@/hooks/helpers/useContributions"; import { calculateShares } from "@/hooks/store/useLeaderBoardStore"; -import { QuestName, useQuestStore } from "@/hooks/store/useQuestStore"; +import { useQuestStore } from "@/hooks/store/useQuestStore"; import { HintModalButton } from "@/ui/elements/HintModalButton"; import { HintSection } from "@/ui/components/hints/HintModal"; +import { QuestId } from "@/ui/components/quest/questDetails"; export const WorldStructuresMenu = ({}: any) => { + const selectedQuest = useQuestStore((state) => state.selectedQuest); + const { hyperstructures } = useHyperstructures(); const { shardMines } = useShardMines(); - const selectedQuest = useQuestStore((state) => state.selectedQuest); - const hyperstructureExtraContent = (entityId: any) => { const hyperstructure = hyperstructures.find((hyperstructure) => hyperstructure.entity_id === BigInt(entityId)); if (!hyperstructure) return null; @@ -64,7 +65,7 @@ export const WorldStructuresMenu = ({}: any) => { ), component: ( } entityContent={hyperstructureExtraContent}