diff --git a/client/apps/game/src/hooks/context/dojo-context.tsx b/client/apps/game/src/hooks/context/dojo-context.tsx index 39abb08d43..f2f2adc07e 100644 --- a/client/apps/game/src/hooks/context/dojo-context.tsx +++ b/client/apps/game/src/hooks/context/dojo-context.tsx @@ -226,7 +226,6 @@ const DojoContextProvider = ({ } } else { if (controllerAccount) { - // console.log("Setting account from controllerAccount:", controllerAccount); useAccountStore.getState().setAccount(controllerAccount); const addressName = runQuery([ @@ -239,7 +238,6 @@ const DojoContextProvider = ({ setAccountsInitialized(true); } else { - // console.log("ControllerAccount is null in production or not connected."); setTimeout(() => { setRetries((prevRetries) => { if (prevRetries < 10) { diff --git a/client/apps/game/src/hooks/helpers/battles/__test__/__mock__.tsx b/client/apps/game/src/hooks/helpers/battles/__test__/__mock__.tsx deleted file mode 100644 index fc6e5cb7b5..0000000000 --- a/client/apps/game/src/hooks/helpers/battles/__test__/__mock__.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { BattleInfo } from "@/hooks/helpers/battles/use-battles"; -import { BattleSide } from "@bibliothecadao/eternum"; -import { Components, ComponentValue } from "@dojoengine/recs"; - -const CONSIDERED_AS_DEAD_HEALTH = 0n; - -export const generateMockBattle = ( - isAttackerAlive: boolean, - isDefenderAlive: boolean, - isStructureBattle: boolean, -): BattleInfo => { - return { - entity_id: 1n, - attack_army: { - troops: { knight_count: 1n, paladin_count: 1n, crossbowman_count: 1n }, - battle_id: 1n, - battle_side: BattleSide.Attack, - }, - attack_army_lifetime: { - troops: { knight_count: 1n, paladin_count: 1n, crossbowman_count: 1n }, - battle_id: 1n, - battle_side: BattleSide.Attack, - }, - defence_army: { - troops: { knight_count: 1n, paladin_count: 1n, crossbowman_count: 1n }, - battle_id: 1n, - battle_side: BattleSide.Defence, - }, - defence_army_lifetime: { - troops: { knight_count: 1n, paladin_count: 1n, crossbowman_count: 1n }, - battle_id: 1n, - battle_side: BattleSide.Defence, - }, - attackers_resources_escrow_id: 1n, - defenders_resources_escrow_id: 1n, - attack_army_health: { current: isAttackerAlive ? 1000n : CONSIDERED_AS_DEAD_HEALTH, lifetime: 1000n }, - defence_army_health: { current: isDefenderAlive ? 1000n : CONSIDERED_AS_DEAD_HEALTH, lifetime: 1000n }, - attack_delta: 1n, - defence_delta: 1n, - last_updated: 1n, - duration_left: 1n, - x: 1, - y: 1, - isStructureBattle: isStructureBattle, - } as unknown as BattleInfo; -}; - -export const generateMockArmy = (battleSide: BattleSide): ComponentValue => { - return { - troops: { knight_count: 1n, paladin_count: 1n, crossbowman_count: 1n }, - battle_id: 1n, - battle_side: battleSide, - }; -}; diff --git a/client/apps/game/src/hooks/helpers/battles/__test__/use-battles._test_default.tsx b/client/apps/game/src/hooks/helpers/battles/__test__/use-battles._test_default.tsx deleted file mode 100644 index 5134b9a34c..0000000000 --- a/client/apps/game/src/hooks/helpers/battles/__test__/use-battles._test_default.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Component, getComponentValue } from "@dojoengine/recs"; -import { describe, it, vi } from "vitest"; -// import { getBattle } from "../useBattles"; - -vi.mock("@dojoengine/recs", async () => { - const actual = await vi.importActual("@dojoengine/recs"); - return { - ...actual, - getComponentValue: vi.fn(), - }; -}); - -const mockComponent = {} as Component; - -describe("getBattle", () => { - it("should return undefined when there are no battles", () => { - vi.mocked(getComponentValue).mockReturnValueOnce(undefined); - - // const battle = getBattle("0x0" as Entity, mockComponent); - // expect(battle).toBeUndefined(); - - // expect(true).toBe(true); - }); - - // it("should return a battle for a valid battleEntityId", () => { - // const mockBattle = generateMockBattle(true, true, false); - // vi.mocked(getComponentValue).mockReturnValueOnce(mockBattle); - - // const expectedBattle = structuredClone(mockBattle); - // const precisionFactor = BigInt(EternumGlobalConfig.resources.resourcePrecision); - - // const adjustHealth = (health: { current: bigint; lifetime: bigint }) => { - // health.current = health.current / precisionFactor; - // health.lifetime = health.lifetime / precisionFactor; - // }; - - // const battle = getBattle("0x123" as Entity, mockComponent); - - // expect(battle?.attack_army_health.current).toBeLessThan(expectedBattle?.attack_army_health.current); - // expect(battle?.defence_army_health.current).toBeLessThan(expectedBattle?.defence_army_health.current); - - // adjustHealth(expectedBattle.attack_army_health); - // adjustHealth(expectedBattle.defence_army_health); - - // expect(getComponentValue).toHaveBeenCalledWith(mockComponent, "0x123"); - - // expect(battle).toBeDefined(); - // expect(battle).toStrictEqual(expectedBattle); - // }); - // }); - - // describe("getExtraBattleInformation", () => { - // it("should return empty array for empty entity array", () => { - // const battles = testedModule.getExtraBattleInformation([], mockComponent, mockComponent, mockComponent); - // expect(battles).toHaveLength(0); - // }); - - // it("should return an empty array if getBattle doesn't return a battle for the entity id", () => { - // vi.spyOn(testedModule, "getBattle").mockReturnValueOnce(undefined); - // const battles = testedModule.getExtraBattleInformation( - // ["0x123" as Entity], - // mockComponent, - // mockComponent, - // mockComponent, - // ); - // expect(battles).toBeDefined(); - // expect(battles).toHaveLength(0); - // }); - - // it("should return an empty array if entity id doesn't return a position", () => { - // const mockBattle = generateMockBattle(true, true, false); - // vi.spyOn(testedModule, "getBattle").mockReturnValueOnce(mockBattle); - - // vi.mocked(getComponentValue).mockReturnValueOnce(undefined); - // const battles = testedModule.getExtraBattleInformation( - // ["0x123" as Entity], - // mockComponent, - // mockComponent, - // mockComponent, - // ); - // expect(battles).toBeDefined(); - // expect(battles).toHaveLength(0); - // }); -}); diff --git a/client/apps/game/src/hooks/helpers/battles/use-battles.tsx b/client/apps/game/src/hooks/helpers/battles/use-battles.tsx deleted file mode 100644 index 1deefc9ea0..0000000000 --- a/client/apps/game/src/hooks/helpers/battles/use-battles.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { useDojo } from "@/hooks/context/dojo-context"; -import { useEntities } from "@/hooks/helpers/use-entities"; -import { BattleManager, ClientComponents, EternumGlobalConfig, ID, Position } from "@bibliothecadao/eternum"; -import { useComponentValue, useEntityQuery } from "@dojoengine/react"; -import { - Component, - ComponentValue, - Components, - Entity, - Has, - HasValue, - NotValue, - getComponentValue, - runQuery, -} from "@dojoengine/recs"; -import { getEntityIdFromKeys } from "@dojoengine/utils"; -import { useMemo } from "react"; - -export type BattleInfo = ComponentValue & { - isStructureBattle: boolean; - position: ComponentValue; -}; - -const getBattle = ( - battleEntityId: Entity, - Battle: Component, -): ComponentValue | undefined => { - let battle = getComponentValue(Battle, battleEntityId); - let battleClone = structuredClone(battle); - if (!battleClone) return; - const multiplier = BigInt(EternumGlobalConfig.resources.resourcePrecision); - battleClone.attack_army_health.current = battleClone.attack_army_health.current / multiplier; - battleClone.attack_army_health.lifetime = battleClone.attack_army_health.lifetime / multiplier; - battleClone.defence_army_health.current = battleClone.defence_army_health.current / multiplier; - battleClone.defence_army_health.lifetime = battleClone.defence_army_health.lifetime / multiplier; - return battleClone; -}; - -const getExtraBattleInformation = ( - battles: Entity[], - Battle: Component, - Position: Component, - Structure: Component, -): BattleInfo[] => { - return battles - .map((battleEntityId) => { - const battle = getBattle(battleEntityId, Battle); - if (!battle) return; - const position = getComponentValue(Position, battleEntityId); - if (!position) return; - const structure = runQuery([Has(Structure), HasValue(Position, { x: position.x, y: position.y })]); - return { ...battle, position, isStructureBattle: structure.size > 0 }; - }) - .filter((item): item is BattleInfo => Boolean(item)); -}; - -export const useBattleManager = (battleEntityId: ID) => { - const dojo = useDojo(); - - const battle = useComponentValue(dojo.setup.components.Battle, getEntityIdFromKeys([BigInt(battleEntityId)])); - - const battleManager = useMemo(() => { - return new BattleManager(dojo.setup.components, dojo.network.provider, battleEntityId); - }, [battleEntityId, battle]); - - return battleManager; -}; - -export const useBattlesByPosition = ({ x, y }: Position) => { - const { - setup: { - components: { Battle, Position, Structure }, - }, - } = useDojo(); - const battleEntityIds = useEntityQuery([Has(Battle), HasValue(Position, { x, y })]); - return getExtraBattleInformation(battleEntityIds, Battle, Position, Structure); -}; - -export const useUserBattles = () => { - const { - setup: { - components: { Army, Battle, EntityOwner, Position, Structure }, - }, - } = useDojo(); - - const { playerRealms } = useEntities(); - const realms = playerRealms(); - - const battles = useMemo(() => { - const battleEntityIds = realms - .map((realm) => { - const userArmiesInBattleEntityIds = runQuery([ - Has(Army), - NotValue(Army, { battle_id: 0 }), - HasValue(EntityOwner, { entity_owner_id: realm.entity_id }), - ]); - const battleEntityIds = Array.from(userArmiesInBattleEntityIds) - .map((armyEntityId) => { - const army = getComponentValue(Army, armyEntityId); - if (!army) return; - return getEntityIdFromKeys([BigInt(army.battle_id)]); - }) - .filter((battleEntityId): battleEntityId is Entity => Boolean(battleEntityId)); - return battleEntityIds; - }) - .flatMap((battleEntityIds) => Array.from(battleEntityIds)); - - return getExtraBattleInformation(battleEntityIds, Battle, Position, Structure); - }, [realms]); - - return battles; -}; diff --git a/client/apps/game/src/hooks/helpers/use-armies.tsx b/client/apps/game/src/hooks/helpers/use-armies.tsx index b826178e1d..ae467db81e 100644 --- a/client/apps/game/src/hooks/helpers/use-armies.tsx +++ b/client/apps/game/src/hooks/helpers/use-armies.tsx @@ -1,246 +1,27 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { type PlayerStructure } from "@/hooks/helpers/use-entities"; -import { - ArmyInfo, - CapacityConfigCategory, - ContractAddress, - EternumGlobalConfig, - getArmyTotalCapacity, - type ClientComponents, - type ID, - type Position, -} from "@bibliothecadao/eternum"; +import { formatArmies } from "@/utils/army"; +import { ArmyInfo, type ID, type Position } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; -import { - Has, - HasValue, - Not, - NotValue, - getComponentValue, - runQuery, - type Component, - type Entity, -} from "@dojoengine/recs"; +import { Has, HasValue, Not, NotValue, runQuery } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { useMemo } from "react"; -import { shortString } from "starknet"; -const formatArmies = ( - armies: Entity[], - playerAddress: string, - Army: Component, - Protectee: Component, - Name: Component, - Health: Component, - Quantity: Component, - Movable: Component, - CapacityConfig: Component, - Weight: Component, - ArrivalTime: Component, - Position: Component, - EntityOwner: Component, - Owner: Component, - Realm: Component, - Stamina: Component, - Structure: Component, -): ArmyInfo[] => { - return armies - .map((armyEntityId) => { - const army = getComponentValue(Army, armyEntityId); - if (!army) return undefined; - - const position = getComponentValue(Position, armyEntityId); - if (!position) return undefined; - - const entityOwner = getComponentValue(EntityOwner, armyEntityId); - if (!entityOwner) return undefined; - - const owner = getComponentValue(Owner, getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)])); - - let health = structuredClone(getComponentValue(Health, armyEntityId)); - if (health) { - health.current = health.current / BigInt(EternumGlobalConfig.resources.resourcePrecision); - health.lifetime = health.lifetime / BigInt(EternumGlobalConfig.resources.resourcePrecision); - } else { - health = { - entity_id: army.entity_id, - current: 0n, - lifetime: 0n, - }; - } - const protectee = getComponentValue(Protectee, armyEntityId); - - let quantity = structuredClone(getComponentValue(Quantity, armyEntityId)); - if (quantity) { - quantity.value = BigInt(quantity.value) / BigInt(EternumGlobalConfig.resources.resourcePrecision); - } else { - quantity = { - entity_id: army.entity_id, - value: 0n, - }; - } - - const movable = getComponentValue(Movable, armyEntityId); - - const armyCapacityConfigEntityId = getEntityIdFromKeys([BigInt(CapacityConfigCategory.Army)]); - const capacity = getComponentValue(CapacityConfig, armyCapacityConfigEntityId); - const totalCapacity = capacity ? getArmyTotalCapacity(army, capacity) : 0n; - - const weightComponentValue = getComponentValue(Weight, armyEntityId); - const weight = weightComponentValue - ? weightComponentValue.value / BigInt(EternumGlobalConfig.resources.resourcePrecision) - : 0n; - - const arrivalTime = getComponentValue(ArrivalTime, armyEntityId); - const stamina = getComponentValue(Stamina, armyEntityId); - const name = getComponentValue(Name, armyEntityId); - const realm = entityOwner && getComponentValue(Realm, getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)])); - const homePosition = realm && getComponentValue(Position, getEntityIdFromKeys([BigInt(realm.entity_id)])); - - const structure = getComponentValue(Structure, getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)])); - - const structurePosition = - structure && getComponentValue(Position, getEntityIdFromKeys([BigInt(structure.entity_id)])); - - const isMine = (owner?.address || 0n) === ContractAddress(playerAddress); - const isMercenary = owner === undefined; - - const isHome = structurePosition && position.x === structurePosition.x && position.y === structurePosition.y; - - return { - ...army, - protectee, - health, - movable, - quantity, - totalCapacity, - weight, - arrivalTime, - position, - entityOwner, - stamina, - owner, - realm, - homePosition, - isMine, - isMercenary, - isHome, - name: name - ? shortString.decodeShortString(name.name.toString()) - : `${protectee ? "🛡️" : "🗡️"}` + `Army ${army.entity_id}`, - }; - }) - .filter((army): army is ArmyInfo => army !== undefined); -}; - -export const useArmiesByEntityOwner = ({ entity_owner_entity_id }: { entity_owner_entity_id: ID }) => { - const { - setup: { - components: { - Position, - EntityOwner, - Owner, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Realm, - Army, - Protectee, - EntityName, - Stamina, - Structure, - }, - }, - account: { account }, - } = useDojo(); - - const armies = useEntityQuery([Has(Army), HasValue(EntityOwner, { entity_owner_id: entity_owner_entity_id })]); - - const entityArmies = useMemo(() => { - return formatArmies( - armies, - account.address, - Army, - Protectee, - EntityName, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Position, - EntityOwner, - Owner, - Realm, - Stamina, - Structure, - ); - }, [armies]); - - return { - entityArmies, - }; -}; - -export const useArmiesByEntityOwnerWithPositionAndQuantity = ({ - entity_owner_entity_id, -}: { - entity_owner_entity_id: ID; -}) => { +export const useArmiesByStructure = ({ structureEntityId }: { structureEntityId: ID }) => { const { - setup: { - components: { - Position, - EntityOwner, - Owner, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Realm, - Army, - Protectee, - EntityName, - Stamina, - Structure, - }, - }, + setup: { components }, account: { account }, } = useDojo(); const armies = useEntityQuery([ - Has(Army), - Has(Position), - Has(Quantity), - HasValue(EntityOwner, { entity_owner_id: entity_owner_entity_id }), + Has(components.Army), + Has(components.Position), + NotValue(components.Health, { current: 0n }), + Has(components.Quantity), + HasValue(components.EntityOwner, { entity_owner_id: structureEntityId }), ]); const entityArmies = useMemo(() => { - return formatArmies( - armies, - account.address, - Army, - Protectee, - EntityName, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Position, - EntityOwner, - Owner, - Realm, - Stamina, - Structure, - ); + return formatArmies(armies, account.address, components); }, [armies]); return { @@ -248,410 +29,66 @@ export const useArmiesByEntityOwnerWithPositionAndQuantity = ({ }; }; -export const getArmiesByBattleId = () => { - const { - setup: { - components: { - Position, - EntityOwner, - Owner, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Realm, - Army, - Protectee, - EntityName, - Stamina, - Structure, - }, - }, - account: { account }, - } = useDojo(); - - const armiesByBattleId = (battle_id: ID) => { - const armiesEntityIds = runQuery([HasValue(Army, { battle_id })]); - return formatArmies( - Array.from(armiesEntityIds), - account.address, - Army, - Protectee, - EntityName, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Position, - EntityOwner, - Owner, - Realm, - Stamina, - Structure, - ); - }; - return armiesByBattleId; -}; - export const useArmyByArmyEntityId = (entityId: ID): ArmyInfo | undefined => { const { - setup: { - components: { - Position, - EntityOwner, - Owner, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Realm, - Army, - Protectee, - EntityName, - Stamina, - Structure, - }, - }, + setup: { components }, account: { account }, } = useDojo(); - const armiesEntityIds = useEntityQuery([HasValue(Army, { entity_id: entityId })]); - return formatArmies( - Array.from(armiesEntityIds), - account.address, - Army, - Protectee, - EntityName, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Position, - EntityOwner, - Owner, - Realm, - Stamina, - Structure, - )[0]; + return formatArmies([getEntityIdFromKeys([BigInt(entityId)])], account.address, components)[0]; }; -export const getUserArmyInBattle = (battle_id: ID) => { +export const useArmiesInBattle = (battle_id: ID) => { const { account: { account }, - setup: { - components: { - Position, - EntityOwner, - Owner, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Realm, - Army, - Protectee, - EntityName, - Stamina, - Structure, - }, - }, + setup: { components }, } = useDojo(); const armiesEntityIds = runQuery([ - Has(Army), - NotValue(Army, { battle_id: 0 }), - HasValue(Army, { battle_id }), - HasValue(Owner, { address: ContractAddress(account.address) }), + Has(components.Army), + NotValue(components.Health, { current: 0n }), + NotValue(components.Army, { battle_id: 0 }), + HasValue(components.Army, { battle_id }), ]); const armies = useMemo(() => { - return formatArmies( - Array.from(armiesEntityIds), - account.address, - Army, - Protectee, - EntityName, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Position, - EntityOwner, - Owner, - Realm, - Stamina, - Structure, - )[0]; + return formatArmies(Array.from(armiesEntityIds), account.address, components); }, [battle_id]); return armies; }; -export const useOwnArmiesByPosition = ({ - position, - inBattle, - playerStructures, -}: { - position: Position; - inBattle: boolean; - playerStructures: PlayerStructure[]; -}) => { +export const useArmiesAtPosition = ({ position }: { position: Position }) => { { const { account: { account }, - setup: { - components: { - Position, - EntityOwner, - Owner, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Realm, - Army, - Protectee, - EntityName, - Stamina, - Structure, - }, - }, + setup: { components }, } = useDojo(); - const ownArmiesAtPosition = useEntityQuery([ - Has(Army), - HasValue(Position, { x: position.x, y: position.y }), - Not(Protectee), - inBattle ? NotValue(Army, { battle_id: 0 }) : HasValue(Army, { battle_id: 0 }), + const armiesAtPosition = useEntityQuery([ + Has(components.Army), + NotValue(components.Health, { current: 0n }), + HasValue(components.Position, { x: position.x, y: position.y }), + Not(components.Protectee), ]); const ownArmies = useMemo(() => { - return formatArmies( - ownArmiesAtPosition, - account.address, - Army, - Protectee, - EntityName, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Position, - EntityOwner, - Owner, - Realm, - Stamina, - Structure, - ).filter((army) => - playerStructures.some((structure) => structure.entity_id === army.entityOwner.entity_owner_id), - ); - }, [ownArmiesAtPosition, position.x, position.y]); + return formatArmies(armiesAtPosition, account.address, components); + }, [armiesAtPosition, position.x, position.y]); return ownArmies; } }; -export const useEnemyArmiesByPosition = ({ - position, - playerStructures, -}: { - position: Position; - playerStructures: PlayerStructure[]; -}) => { - { - const { - account: { account }, - setup: { - components: { - Position, - EntityOwner, - Owner, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Realm, - Army, - Protectee, - EntityName, - Stamina, - Structure, - }, - }, - } = useDojo(); - - const enemyArmiesAtPosition = useEntityQuery([ - Has(Army), - HasValue(Position, { x: position.x, y: position.y }), - Not(Protectee), - ]); - - const enemyArmies = useMemo(() => { - return formatArmies( - enemyArmiesAtPosition, - account.address, - Army, - Protectee, - EntityName, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Position, - EntityOwner, - Owner, - Realm, - Stamina, - Structure, - ).filter((army) => - playerStructures.every((structure) => structure.entity_id !== army.entityOwner.entity_owner_id), - ); - }, [enemyArmiesAtPosition]); - - return enemyArmies; - } -}; - -export const getArmyByEntityId = () => { +export const useGetArmyByEntityId = () => { const { - setup: { - components: { - Position, - EntityOwner, - Owner, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Realm, - Army, - Protectee, - EntityName, - Stamina, - Structure, - }, - }, + setup: { components }, account: { account }, } = useDojo(); - const getAliveArmy = (entity_id: ID): ArmyInfo | undefined => { - const armiesEntityIds = runQuery([Has(Army), HasValue(Army, { entity_id })]); - return formatArmies( - Array.from(armiesEntityIds), - account.address, - Army, - Protectee, - EntityName, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Position, - EntityOwner, - Owner, - Realm, - Stamina, - Structure, - )[0]; - }; - const getArmy = (entity_id: ID): ArmyInfo | undefined => { - const armiesEntityIds = runQuery([Has(Army), HasValue(Army, { entity_id })]); - - return formatArmies( - Array.from(armiesEntityIds), - account.address, - Army, - Protectee, - EntityName, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Position, - EntityOwner, - Owner, - Realm, - Stamina, - Structure, - )[0]; - }; - - return { getAliveArmy, getArmy }; -}; - -export const getArmiesByPosition = () => { - const { - account: { account }, - setup: { - components: { - Position, - EntityOwner, - Owner, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Realm, - Army, - Protectee, - EntityName, - Stamina, - Structure, - }, - }, - } = useDojo(); - - const getArmies = (position: Position) => { - const armiesEntityIds = runQuery([Has(Army), HasValue(Position, { x: position.x, y: position.y })]); - return formatArmies( - Array.from(armiesEntityIds), - account.address, - Army, - Protectee, - EntityName, - Health, - Quantity, - Movable, - CapacityConfig, - Weight, - ArrivalTime, - Position, - EntityOwner, - Owner, - Realm, - Stamina, - Structure, - ); + return formatArmies([getEntityIdFromKeys([BigInt(entity_id)])], account.address, components)[0]; }; - return getArmies; + return { getArmy }; }; diff --git a/client/apps/game/src/hooks/helpers/use-banks.tsx b/client/apps/game/src/hooks/helpers/use-banks.tsx index eb48489f0f..a27e783907 100644 --- a/client/apps/game/src/hooks/helpers/use-banks.tsx +++ b/client/apps/game/src/hooks/helpers/use-banks.tsx @@ -1,45 +1,32 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { ContractAddress, ID, Position } from "@bibliothecadao/eternum"; -import { useEntityQuery } from "@dojoengine/react"; -import { Has, HasValue, getComponentValue } from "@dojoengine/recs"; +import { ADMIN_BANK_ENTITY_ID } from "@bibliothecadao/eternum"; +import { getComponentValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; +import { shortString } from "starknet"; -export const useGetBanks = (onlyMine?: boolean) => { +export const useBank = () => { const { - account: { account }, setup: { components: { Bank, Position, Owner, AddressName }, }, } = useDojo(); - const query = onlyMine ? [Has(Bank), HasValue(Owner, { address: ContractAddress(account.address) })] : [Has(Bank)]; - const entityIds = useEntityQuery(query); + const entity = getEntityIdFromKeys([BigInt(ADMIN_BANK_ENTITY_ID)]); - return entityIds - .map((entityId) => { - const position = getComponentValue(Position, entityId); - if (!position) return; + const position = getComponentValue(Position, entity); + if (!position) return; - const owner = getComponentValue(Owner, entityId); - const addressName = getComponentValue(AddressName, getEntityIdFromKeys([BigInt(owner?.address || "0x0")])); + const owner = getComponentValue(Owner, entity); + const addressName = getComponentValue(AddressName, getEntityIdFromKeys([BigInt(owner?.address || "0x0")])); - const bank = getComponentValue(Bank, entityId); + const bank = getComponentValue(Bank, entity); - return { - entityId: position.entity_id, - position: { x: position.x, y: position.y }, - owner: addressName?.name || "Bandits", - ownerFee: bank ? Number(bank.owner_fee_num) / Number(bank.owner_fee_denom) : 0, - depositFee: bank ? Number(bank.owner_bridge_fee_dpt_percent) : 0, - withdrawFee: bank ? Number(bank.owner_bridge_fee_wtdr_percent) : 0, - }; - }) - .filter(Boolean) as { - entityId: ID; - position: Position; - owner: string; - ownerFee: number; - depositFee: number; - withdrawFee: number; - }[]; + return { + entityId: position.entity_id, + position: { x: position.x, y: position.y }, + owner: addressName?.name ? shortString.decodeShortString(addressName.name.toString()) : "Bandits", + ownerFee: bank ? Number(bank.owner_fee_num) / Number(bank.owner_fee_denom) : 0, + depositFee: bank ? Number(bank.owner_bridge_fee_dpt_percent) : 0, + withdrawFee: bank ? Number(bank.owner_bridge_fee_wtdr_percent) : 0, + }; }; diff --git a/client/apps/game/src/hooks/helpers/use-battles.tsx b/client/apps/game/src/hooks/helpers/use-battles.tsx new file mode 100644 index 0000000000..9deae40585 --- /dev/null +++ b/client/apps/game/src/hooks/helpers/use-battles.tsx @@ -0,0 +1,75 @@ +import { useDojo } from "@/hooks/context/dojo-context"; +import { useEntities } from "@/hooks/helpers/use-entities"; +import { BattleManager, ID, Position } from "@bibliothecadao/eternum"; +import { useComponentValue, useEntityQuery } from "@dojoengine/react"; +import { Has, HasValue, NotValue, getComponentValue, runQuery } from "@dojoengine/recs"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import { useMemo } from "react"; + +export const useBattleManager = (battleEntityId: ID) => { + const dojo = useDojo(); + + const battle = useComponentValue(dojo.setup.components.Battle, getEntityIdFromKeys([BigInt(battleEntityId)])); + + const battleManager = useMemo(() => { + return new BattleManager(dojo.setup.components, dojo.network.provider, battleEntityId); + }, [battleEntityId, battle]); + + return battleManager; +}; + +export const useBattlesAtPosition = ({ x, y }: Position) => { + const { + setup: { + components: { Battle, Position }, + }, + } = useDojo(); + const battles = useEntityQuery([Has(Battle), NotValue(Battle, { duration_left: 0n }), HasValue(Position, { x, y })]); + + const battleEntityIds = useMemo(() => { + return Array.from(battles) + .map((battleId) => { + const battle = getComponentValue(Battle, battleId); + return battle ? battle.entity_id : undefined; + }) + .filter((id): id is ID => Boolean(id)) as ID[]; + }, [battles]); + + return battleEntityIds; +}; + +export const usePlayerBattles = () => { + const { + setup: { + components: { Army, EntityOwner }, + }, + } = useDojo(); + + const { playerRealms } = useEntities(); + const realms = playerRealms(); + + const battleEntityIds = useMemo(() => { + // Get all armies in battle owned by player's realms + const armiesInBattle = realms.flatMap((realm) => + Array.from( + runQuery([ + Has(Army), + NotValue(Army, { battle_id: 0 }), + HasValue(EntityOwner, { entity_owner_id: realm.entity_id }), + ]), + ), + ); + + // Map armies to their battle IDs + const battleIds = armiesInBattle + .map((armyId) => { + const army = getComponentValue(Army, armyId); + return army ? army.battle_id : undefined; + }) + .filter((id) => Boolean(id)) as ID[]; + + return battleIds; + }, [realms]); + + return battleEntityIds; +}; diff --git a/client/apps/game/src/hooks/helpers/use-quests.tsx b/client/apps/game/src/hooks/helpers/use-quests.tsx index 8de6a56f6c..e8bea46a71 100644 --- a/client/apps/game/src/hooks/helpers/use-quests.tsx +++ b/client/apps/game/src/hooks/helpers/use-quests.tsx @@ -1,5 +1,5 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useArmiesByEntityOwnerWithPositionAndQuantity } from "@/hooks/helpers/use-armies"; +import { useArmiesByStructure } from "@/hooks/helpers/use-armies"; import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import { useGetMyOffers } from "@/hooks/helpers/use-trade"; import useUIStore from "@/hooks/store/use-ui-store"; @@ -59,8 +59,8 @@ const useQuestDependencies = () => { HasValue(setup.components.EntityOwner, { entity_owner_id: structureEntityId || 0 }), ]); const buildingQuantities = useBuildingQuantities(structureEntityId); - const { entityArmies } = useArmiesByEntityOwnerWithPositionAndQuantity({ - entity_owner_entity_id: structureEntityId || 0, + const { entityArmies } = useArmiesByStructure({ + structureEntityId: structureEntityId || 0, }); const orders = useGetMyOffers(); const { getEntityInfo } = useEntitiesUtils(); diff --git a/client/apps/game/src/hooks/helpers/use-structures.tsx b/client/apps/game/src/hooks/helpers/use-structures.tsx index e57abae62c..a771ec3946 100644 --- a/client/apps/game/src/hooks/helpers/use-structures.tsx +++ b/client/apps/game/src/hooks/helpers/use-structures.tsx @@ -1,6 +1,6 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { getArmyByEntityId } from "@/hooks/helpers/use-armies"; +import { useGetArmyByEntityId } from "@/hooks/helpers/use-armies"; import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; import { currentTickCount } from "@/ui/utils/utils"; @@ -26,7 +26,7 @@ export const useStructureAtPosition = ({ x, y }: Position): Structure | undefine }, } = useDojo(); - const { getAliveArmy } = getArmyByEntityId(); + const { getArmy } = useGetArmyByEntityId(); const { getEntityName } = useEntitiesUtils(); @@ -43,7 +43,7 @@ export const useStructureAtPosition = ({ x, y }: Position): Structure | undefine const owner = ownerOnChain ? ownerOnChain : { entity_id: structure.entity_id, address: ContractAddress(0n) }; const protectorArmy = getComponentValue(Protector, structureEntityId); - const protector = protectorArmy ? getAliveArmy(protectorArmy.army_id) : undefined; + const protector = protectorArmy ? getArmy(protectorArmy.army_id) : undefined; const name = getEntityName(structure.entity_id) || ""; @@ -73,7 +73,7 @@ export const useStructureByPosition = () => { }, } = useDojo(); - const { getAliveArmy } = getArmyByEntityId(); + const { getArmy } = useGetArmyByEntityId(); const { getEntityName } = useEntitiesUtils(); @@ -90,7 +90,7 @@ export const useStructureByPosition = () => { const owner = ownerOnChain ? ownerOnChain : { entity_id: structure.entity_id, address: ContractAddress(0n) }; const protectorArmy = getComponentValue(Protector, structureEntityId); - const protector = protectorArmy ? getAliveArmy(protectorArmy.army_id) : undefined; + const protector = protectorArmy ? getArmy(protectorArmy.army_id) : undefined; const name = getEntityName(structure.entity_id); @@ -118,7 +118,7 @@ export const useStructureByEntityId = (entityId: ID) => { const { getEntityName } = useEntitiesUtils(); - const { getAliveArmy } = getArmyByEntityId(); + const { getArmy } = useGetArmyByEntityId(); const structure = useMemo(() => { const structureEntityId = getEntityIdFromKeys([BigInt(entityId)]); @@ -132,7 +132,7 @@ export const useStructureByEntityId = (entityId: ID) => { const owner = ownerOnChain ? ownerOnChain : { entity_id: structure.entity_id, address: ContractAddress(0n) }; const protectorArmy = getComponentValue(Protector, structureEntityId); - const protector = protectorArmy ? getAliveArmy(protectorArmy.army_id) : undefined; + const protector = protectorArmy ? getArmy(protectorArmy.army_id) : undefined; const addressName = getComponentValue(AddressName, getEntityIdFromKeys([owner?.address])); const ownerName = addressName ? shortString.decodeShortString(addressName!.name.toString()) : "Bandits"; @@ -165,7 +165,7 @@ export const useStructures = () => { }, } = useDojo(); - const { getAliveArmy } = getArmyByEntityId(); + const { getArmy } = useGetArmyByEntityId(); const { getEntityName } = useEntitiesUtils(); const getStructureByEntityId = (entityId: ID) => { @@ -180,7 +180,7 @@ export const useStructures = () => { const owner = ownerOnChain ? ownerOnChain : { entity_id: structure.entity_id, address: ContractAddress(0n) }; const protectorArmy = getComponentValue(Protector, structureEntityId); - const protector = protectorArmy ? getAliveArmy(protectorArmy.army_id) : undefined; + const protector = protectorArmy ? getArmy(protectorArmy.army_id) : undefined; const addressName = getComponentValue(AddressName, getEntityIdFromKeys([owner?.address])); const ownerName = addressName ? shortString.decodeShortString(addressName!.name.toString()) : "Bandits"; diff --git a/client/apps/game/src/three/managers/input-manager.ts b/client/apps/game/src/three/managers/input-manager.ts index a62f72be5f..3059398ef4 100644 --- a/client/apps/game/src/three/managers/input-manager.ts +++ b/client/apps/game/src/three/managers/input-manager.ts @@ -7,7 +7,6 @@ type ListenerTypes = "click" | "mousemove" | "contextmenu" | "dblclick" | "mouse export class InputManager { private listeners: Array<{ event: ListenerTypes; handler: (e: MouseEvent) => void }> = []; private isDragged = false; - private clickTimer: NodeJS.Timeout | null = null; // Add this property constructor( private sceneName: SceneName, diff --git a/client/apps/game/src/ui/components/battles/battle-list-item.tsx b/client/apps/game/src/ui/components/battles/battle-list-item.tsx index 971f0727bc..6f419fc13b 100644 --- a/client/apps/game/src/ui/components/battles/battle-list-item.tsx +++ b/client/apps/game/src/ui/components/battles/battle-list-item.tsx @@ -2,24 +2,22 @@ import { ReactComponent as Inventory } from "@/assets/icons/common/bagpack.svg"; import { ReactComponent as Sword } from "@/assets/icons/common/cross-swords.svg"; import { ReactComponent as Eye } from "@/assets/icons/common/eye.svg"; import { useDojo } from "@/hooks/context/dojo-context"; -import { BattleInfo } from "@/hooks/helpers/battles/use-battles"; import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import useUIStore from "@/hooks/store/use-ui-store"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; import { ViewOnMapIcon } from "@/ui/components/military/army-management-card"; import { TroopDisplay } from "@/ui/components/military/troop-chip"; import { InventoryResources } from "@/ui/components/resources/inventory-resources"; -import { ArmyInfo, BattleManager } from "@bibliothecadao/eternum"; -import { getComponentValue, HasValue, runQuery } from "@dojoengine/recs"; +import { ArmyInfo, BattleManager, ID } from "@bibliothecadao/eternum"; import React, { useMemo, useState } from "react"; type BattleListItemProps = { - battle: BattleInfo; - ownArmySelected: ArmyInfo | undefined; + battleEntityId: ID; + ownArmySelected?: ArmyInfo; showCompass?: boolean; }; -export const BattleListItem = ({ battle, ownArmySelected, showCompass = false }: BattleListItemProps) => { +export const BattleListItem = ({ battleEntityId, ownArmySelected, showCompass = false }: BattleListItemProps) => { const dojo = useDojo(); const { getAddressNameFromEntity } = useEntitiesUtils(); @@ -32,22 +30,25 @@ export const BattleListItem = ({ battle, ownArmySelected, showCompass = false }: const setTooltip = useUIStore((state) => state.setTooltip); const battleManager = useMemo( - () => new BattleManager(dojo.setup.components, dojo.network.provider, battle.entity_id), - [battle], + () => new BattleManager(dojo.setup.components, dojo.network.provider, battleEntityId), + [battleEntityId], ); const updatedBattle = useMemo(() => { const updatedBattle = battleManager.getUpdatedBattle(nextBlockTimestamp!); return updatedBattle; - }, [nextBlockTimestamp]); + }, [nextBlockTimestamp, battleManager]); const armiesInBattle = useMemo(() => { - const armiesEntityIds = runQuery([ - HasValue(dojo.setup.components.Army, { battle_id: battleManager.battleEntityId }), - ]); - return Array.from(armiesEntityIds).map( - (entityId) => getComponentValue(dojo.setup.components.Army, entityId)!.entity_id, - ); + return battleManager.getArmiesInBattle(); + }, [battleManager]); + + const escrowIds = useMemo(() => { + return battleManager.getEscrowIds(); + }, [battleManager]); + + const battlePosition = useMemo(() => { + return battleManager.getPosition(); }, [battleManager]); const buttons = useMemo(() => { @@ -100,7 +101,7 @@ export const BattleListItem = ({ battle, ownArmySelected, showCompass = false }:
- {showCompass && } + {showCompass && battlePosition && }
@@ -128,12 +129,12 @@ export const BattleListItem = ({ battle, ownArmySelected, showCompass = false }: {showInventory && (
diff --git a/client/apps/game/src/ui/components/entities/entity.tsx b/client/apps/game/src/ui/components/entities/entity.tsx index 8cc3f4133d..27fb5c3efc 100644 --- a/client/apps/game/src/ui/components/entities/entity.tsx +++ b/client/apps/game/src/ui/components/entities/entity.tsx @@ -1,5 +1,5 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { getArmyByEntityId } from "@/hooks/helpers/use-armies"; +import { useGetArmyByEntityId } from "@/hooks/helpers/use-armies"; import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import { ArrivalInfo } from "@/hooks/helpers/use-resource-arrivals"; import { useResourcesUtils } from "@/hooks/helpers/use-resources"; @@ -38,7 +38,7 @@ export const EntityArrival = ({ arrival, ...props }: EntityProps) => { const { getEntityInfo, getEntityName } = useEntitiesUtils(); const { getResourcesFromBalance } = useResourcesUtils(); const { nextBlockTimestamp } = useNextBlockTimestamp(); - const { getArmy } = getArmyByEntityId(); + const { getArmy } = useGetArmyByEntityId(); const weight = useComponentValue(dojo.setup.components.Weight, getEntityIdFromKeys([BigInt(arrival.entityId)])); diff --git a/client/apps/game/src/ui/components/military/army-chip.tsx b/client/apps/game/src/ui/components/military/army-chip.tsx index 225f429450..d510efe922 100644 --- a/client/apps/game/src/ui/components/military/army-chip.tsx +++ b/client/apps/game/src/ui/components/military/army-chip.tsx @@ -3,7 +3,7 @@ import { ReactComponent as Plus } from "@/assets/icons/common/plus-sign.svg"; import { ReactComponent as Swap } from "@/assets/icons/common/swap.svg"; import { ReactComponent as Compass } from "@/assets/icons/compass.svg"; import { useDojo } from "@/hooks/context/dojo-context"; -import { getArmiesByPosition } from "@/hooks/helpers/use-armies"; +import { useArmiesAtPosition } from "@/hooks/helpers/use-armies"; import { armyHasTroops } from "@/hooks/helpers/use-quests"; import useUIStore from "@/hooks/store/use-ui-store"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; @@ -227,13 +227,11 @@ const ArmyMergeTroopsPanel = ({ }) => { const [selectedReceiverArmy, setSelectedReceiverArmy] = useState(null); - const getArmies = getArmiesByPosition(); + const armiesAtPosition = useArmiesAtPosition({ position: giverArmy.position }); const armies = useMemo(() => { - return getArmies({ x: giverArmy.position.x, y: giverArmy.position.y }).filter( - (army) => army.entity_id !== giverArmy.entity_id, - ); - }, [giverArmy]); + return armiesAtPosition.filter((army) => army.entity_id !== giverArmy.entity_id); + }, [giverArmy, armiesAtPosition]); return (
diff --git a/client/apps/game/src/ui/components/military/army-list.tsx b/client/apps/game/src/ui/components/military/army-list.tsx index ee3b4ec47c..08ff164c80 100644 --- a/client/apps/game/src/ui/components/military/army-list.tsx +++ b/client/apps/game/src/ui/components/military/army-list.tsx @@ -1,6 +1,6 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useArmiesByEntityOwner } from "@/hooks/helpers/use-armies"; +import { useArmiesByStructure } from "@/hooks/helpers/use-armies"; import { type PlayerStructure } from "@/hooks/helpers/use-entities"; import useUIStore from "@/hooks/store/use-ui-store"; import { HintSection } from "@/ui/components/hints/hint-modal"; @@ -29,8 +29,8 @@ export const EntityArmyList = ({ structure }: { structure: PlayerStructure }) => }); const existingBuildings = tileManager.existingBuildings(); - const { entityArmies: structureArmies } = useArmiesByEntityOwner({ - entity_owner_entity_id: structure?.entity_id || 0, + const { entityArmies: structureArmies } = useArmiesByStructure({ + structureEntityId: structure?.entity_id || 0, }); const { diff --git a/client/apps/game/src/ui/components/military/entities-army-table.tsx b/client/apps/game/src/ui/components/military/entities-army-table.tsx index 173baa7b75..1255e80039 100644 --- a/client/apps/game/src/ui/components/military/entities-army-table.tsx +++ b/client/apps/game/src/ui/components/military/entities-army-table.tsx @@ -1,4 +1,4 @@ -import { useArmiesByEntityOwner } from "@/hooks/helpers/use-armies"; +import { useArmiesByStructure } from "@/hooks/helpers/use-armies"; import { useEntities } from "@/hooks/helpers/use-entities"; import useUIStore from "@/hooks/store/use-ui-store"; import { HintSection } from "@/ui/components/hints/hint-modal"; @@ -52,7 +52,7 @@ const EntityArmyTable = ({ structureEntityId }: { structureEntityId: ID | undefi if (!structureEntityId) { return
Entity not found
; } - const { entityArmies } = useArmiesByEntityOwner({ entity_owner_entity_id: structureEntityId }); + const { entityArmies } = useArmiesByStructure({ structureEntityId }); const totalTroops = entityArmies.reduce( (acc, army: ArmyInfo) => { diff --git a/client/apps/game/src/ui/components/military/user-battles.tsx b/client/apps/game/src/ui/components/military/user-battles.tsx index f10780da75..f3aa66fcaa 100644 --- a/client/apps/game/src/ui/components/military/user-battles.tsx +++ b/client/apps/game/src/ui/components/military/user-battles.tsx @@ -1,18 +1,18 @@ -import { useUserBattles } from "@/hooks/helpers/battles/use-battles"; +import { usePlayerBattles } from "@/hooks/helpers/use-battles"; import { BattleListItem } from "@/ui/components/battles/battle-list-item"; export const UserBattles = () => { - const battles = useUserBattles(); + const battleEntityIds = usePlayerBattles(); return (
- {battles.length > 0 && ( + {battleEntityIds.length > 0 && ( <>
Your battles
- {battles - .sort((a, b) => Number(a.duration_left) - Number(b.duration_left)) - .map((battle) => ( - + {battleEntityIds + // .sort((a, b) => Number(a.duration_left) - Number(b.duration_left)) + .map((id) => ( + ))} )} diff --git a/client/apps/game/src/ui/components/structures/worldmap/structure-card.tsx b/client/apps/game/src/ui/components/structures/worldmap/structure-card.tsx index 5731dd23a2..4160a79fce 100644 --- a/client/apps/game/src/ui/components/structures/worldmap/structure-card.tsx +++ b/client/apps/game/src/ui/components/structures/worldmap/structure-card.tsx @@ -1,6 +1,6 @@ import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { getArmyByEntityId } from "@/hooks/helpers/use-armies"; +import { useGetArmyByEntityId } from "@/hooks/helpers/use-armies"; import { useGuilds } from "@/hooks/helpers/use-guilds"; import { useQuery } from "@/hooks/helpers/use-query"; import { @@ -212,7 +212,7 @@ const TroopExchange = ({ }, } = useDojo(); - const { getArmy } = getArmyByEntityId(); + const { getArmy } = useGetArmyByEntityId(); const maxTroopCountPerArmy = configManager.getTroopConfig().maxTroopCount; diff --git a/client/apps/game/src/ui/components/trading/market-modal.tsx b/client/apps/game/src/ui/components/trading/market-modal.tsx index 2dcefa273f..f882517ae9 100644 --- a/client/apps/game/src/ui/components/trading/market-modal.tsx +++ b/client/apps/game/src/ui/components/trading/market-modal.tsx @@ -5,9 +5,9 @@ import { ReactComponent as Sparkles } from "@/assets/icons/sparkles.svg"; import { ReactComponent as Swap } from "@/assets/icons/swap.svg"; import { configManager } from "@/dojo/setup"; import { useDojo } from "@/hooks/context/dojo-context"; -import { useBattlesByPosition } from "@/hooks/helpers/battles/use-battles"; import { useArmyByArmyEntityId } from "@/hooks/helpers/use-armies"; -import { useGetBanks } from "@/hooks/helpers/use-banks"; +import { useBank } from "@/hooks/helpers/use-banks"; +import { useBattlesAtPosition } from "@/hooks/helpers/use-battles"; import { useEntities } from "@/hooks/helpers/use-entities"; import { useStructureByPosition } from "@/hooks/helpers/use-structures"; import { useSetMarket } from "@/hooks/helpers/use-trade"; @@ -61,27 +61,24 @@ export const MarketModal = () => { const { playerStructures } = useEntities(); const { toggleModal } = useModalStore(); - const banks = useGetBanks(); + const bank = useBank(); const { bidOffers, askOffers } = useSetMarket(); - const bank = banks.length === 1 ? banks[0] : null; - const battles = useBattlesByPosition(bank?.position || { x: 0, y: 0 }); + const battles = useBattlesAtPosition(bank?.position || { x: 0, y: 0 }); const currentBlockTimestamp = useUIStore.getState().nextBlockTimestamp || 0; const getStructure = useStructureByPosition(); const bankStructure = getStructure(bank?.position || { x: 0, y: 0 }); - const battle = useMemo(() => { + const battleEntityId = useMemo(() => { if (battles.length === 0) return null; - return battles - .filter((battle) => battle.isStructureBattle) - .sort((a, b) => Number(a.last_updated || 0) - Number(b.last_updated || 0))[0]; + return battles[0]; }, [battles]); const battleManager = useMemo( - () => new BattleManager(dojo.setup.components, dojo.network.provider, battle?.entity_id || 0), - [battle?.entity_id], + () => new BattleManager(dojo.setup.components, dojo.network.provider, battleEntityId || 0), + [battleEntityId], ); // initial entity id diff --git a/client/apps/game/src/ui/components/worldmap/armies/army-info-label.tsx b/client/apps/game/src/ui/components/worldmap/armies/army-info-label.tsx index 32e4b02e24..ecc7b412e1 100644 --- a/client/apps/game/src/ui/components/worldmap/armies/army-info-label.tsx +++ b/client/apps/game/src/ui/components/worldmap/armies/army-info-label.tsx @@ -1,4 +1,4 @@ -import { getArmyByEntityId } from "@/hooks/helpers/use-armies"; +import { useGetArmyByEntityId } from "@/hooks/helpers/use-armies"; import { useQuery } from "@/hooks/helpers/use-query"; import { useRealm } from "@/hooks/helpers/use-realm"; import { useIsStructureImmune, useStructureImmunityTimer, useStructures } from "@/hooks/helpers/use-structures"; @@ -20,7 +20,7 @@ import { useMemo } from "react"; export const ArmyInfoLabel = () => { const { isMapView } = useQuery(); const hoveredArmyEntityId = useUIStore((state) => state.hoveredArmyEntityId); - const { getArmy } = getArmyByEntityId(); + const { getArmy } = useGetArmyByEntityId(); const army = useMemo(() => { if (hoveredArmyEntityId) return getArmy(hoveredArmyEntityId); diff --git a/client/apps/game/src/ui/components/worldmap/armies/selected-army.tsx b/client/apps/game/src/ui/components/worldmap/armies/selected-army.tsx index eacd05ff34..60851e61ac 100644 --- a/client/apps/game/src/ui/components/worldmap/armies/selected-army.tsx +++ b/client/apps/game/src/ui/components/worldmap/armies/selected-army.tsx @@ -1,5 +1,4 @@ -import { useOwnArmiesByPosition } from "@/hooks/helpers/use-armies"; -import { useEntities } from "@/hooks/helpers/use-entities"; +import { useArmiesAtPosition } from "@/hooks/helpers/use-armies"; import { useQuery } from "@/hooks/helpers/use-query"; import useUIStore from "@/hooks/store/use-ui-store"; import { Position } from "@/types/position"; @@ -20,39 +19,36 @@ export const SelectedArmy = () => { if (!selectedHex) updateSelectedEntityId(null); }, [selectedHex, updateSelectedEntityId]); - const { playerStructures } = useEntities(); - - const rawArmies = useOwnArmiesByPosition({ + const armies = useArmiesAtPosition({ position: new Position({ x: selectedHex?.col || 0, y: selectedHex?.row || 0 }).getContract(), - inBattle: false, - playerStructures: playerStructures(), }); - const userArmies = useMemo(() => rawArmies.filter((army) => army.health.current > 0), [rawArmies]); + // player armies that are not in battle + const playerArmies = useMemo(() => armies.filter((army) => army.isMine && army.battle_id === 0), [armies]); useEffect(() => { setSelectedArmyIndex(0); - }, [userArmies]); + }, [playerArmies]); useEffect(() => { if (selectedHex) { - updateSelectedEntityId(userArmies[selectedArmyIndex]?.entity_id || 0); + updateSelectedEntityId(playerArmies[selectedArmyIndex]?.entity_id || 0); } - }, [selectedArmyIndex, userArmies, updateSelectedEntityId, selectedHex]); + }, [selectedArmyIndex, playerArmies, updateSelectedEntityId, selectedHex]); const ownArmy = useMemo( - () => userArmies.find((army) => army.entity_id === selectedEntityId), - [userArmies, selectedEntityId], + () => playerArmies.find((army) => army.entity_id === selectedEntityId), + [playerArmies, selectedEntityId], ); const handleKeyDown = useCallback( (event: KeyboardEvent) => { if (event.key === "Tab") { event.preventDefault(); - setSelectedArmyIndex((prevIndex) => (prevIndex + 1) % userArmies.length); + setSelectedArmyIndex((prevIndex) => (prevIndex + 1) % playerArmies.length); } }, - [userArmies.length], + [playerArmies.length], ); useEffect(() => { @@ -74,11 +70,11 @@ export const SelectedArmy = () => { > {showTooltip && (
- {userArmies.length > 1 && ( + {playerArmies.length > 1 && (
Press Tab to cycle through armies
- Army {selectedArmyIndex + 1}/{userArmies.length} + Army {selectedArmyIndex + 1}/{playerArmies.length}
)} diff --git a/client/apps/game/src/ui/components/worldmap/battles/battle-label.tsx b/client/apps/game/src/ui/components/worldmap/battles/battle-label.tsx index 259343f8f6..a258b77b75 100644 --- a/client/apps/game/src/ui/components/worldmap/battles/battle-label.tsx +++ b/client/apps/game/src/ui/components/worldmap/battles/battle-label.tsx @@ -1,5 +1,5 @@ -import { DojoResult, useDojo } from "@/hooks/context/dojo-context"; -import { useBattlesByPosition } from "@/hooks/helpers/battles/use-battles"; +import { useDojo } from "@/hooks/context/dojo-context"; +import { useBattlesAtPosition } from "@/hooks/helpers/use-battles"; import { useQuery } from "@/hooks/helpers/use-query"; import { useStructureByPosition } from "@/hooks/helpers/use-structures"; import useUIStore from "@/hooks/store/use-ui-store"; @@ -11,16 +11,12 @@ import { BattleManager, Structure } from "@bibliothecadao/eternum"; import { useMemo } from "react"; export const BattleInfoLabel = () => { - const dojo = useDojo(); const { isMapView } = useQuery(); const getStructure = useStructureByPosition(); const hoveredBattlePosition = useUIStore((state) => state.hoveredBattle); const currentTimestamp = useUIStore.getState().nextBlockTimestamp || 0; - const battles = useBattlesByPosition({ x: hoveredBattlePosition?.x || 0, y: hoveredBattlePosition?.y || 0 }).filter( - (battle) => battle.duration_left > 0, - ); - + const battles = useBattlesAtPosition({ x: hoveredBattlePosition?.x || 0, y: hoveredBattlePosition?.y || 0 }); const structure = getStructure({ x: hoveredBattlePosition?.x || 0, y: hoveredBattlePosition?.y || 0 }); return ( @@ -33,9 +29,8 @@ export const BattleInfoLabel = () => { {battles.map((battle) => ( @@ -49,15 +44,15 @@ export const BattleInfoLabel = () => { const BattleInfo = ({ battleEntityId, - dojo, currentTimestamp, structure, }: { battleEntityId: number; - dojo: DojoResult; currentTimestamp: number; structure: Structure | undefined; }) => { + const dojo = useDojo(); + const battleManager = useMemo( () => new BattleManager(dojo.setup.components, dojo.network.provider, battleEntityId), [battleEntityId, dojo], diff --git a/client/apps/game/src/ui/components/worldmap/structures/structure-list-item.tsx b/client/apps/game/src/ui/components/worldmap/structures/structure-list-item.tsx index 8b7743d437..efe8ba7768 100644 --- a/client/apps/game/src/ui/components/worldmap/structures/structure-list-item.tsx +++ b/client/apps/game/src/ui/components/worldmap/structures/structure-list-item.tsx @@ -2,7 +2,7 @@ import { ReactComponent as Sword } from "@/assets/icons/common/cross-swords.svg" import { ReactComponent as Eye } from "@/assets/icons/common/eye.svg"; import { ReactComponent as Shield } from "@/assets/icons/common/shield.svg"; import { useDojo } from "@/hooks/context/dojo-context"; -import { getUserArmyInBattle } from "@/hooks/helpers/use-armies"; +import { useArmiesInBattle } from "@/hooks/helpers/use-armies"; import { useGetHyperstructureProgress } from "@/hooks/helpers/use-hyperstructures"; import { useIsStructureImmune } from "@/hooks/helpers/use-structures"; import useUIStore from "@/hooks/store/use-ui-store"; @@ -62,7 +62,12 @@ export const StructureListItem = ({ return { updatedBattle }; }, [nextBlockTimestamp]); - const userArmyInBattle = getUserArmyInBattle(updatedBattle?.entity_id || 0); + const armiesInBattle = useArmiesInBattle(updatedBattle?.entity_id || 0); + + // Filter out only the player's armies + const playerArmiesInBattle = useMemo(() => { + return armiesInBattle.filter((army) => army.isMine); + }, [armiesInBattle]); const isImmune = useIsStructureImmune(structure, nextBlockTimestamp!); @@ -129,7 +134,7 @@ export const StructureListItem = ({ if (structure.isMine) { return [shieldButton]; } - if (userArmyInBattle) { + if (playerArmiesInBattle.length > 0) { return [eyeButton]; } return [ @@ -160,7 +165,14 @@ export const StructureListItem = ({ />, ]; } - }, [nextBlockTimestamp, userArmyInBattle, ownArmySelected, updatedBattle, setBattleView, setShowMergeTroopsPopup]); + }, [ + nextBlockTimestamp, + playerArmiesInBattle, + ownArmySelected, + updatedBattle, + setBattleView, + setShowMergeTroopsPopup, + ]); return (
diff --git a/client/apps/game/src/ui/modules/chat/utils.tsx b/client/apps/game/src/ui/modules/chat/utils.tsx index 1b128db8ff..b0313ffa67 100644 --- a/client/apps/game/src/ui/modules/chat/utils.tsx +++ b/client/apps/game/src/ui/modules/chat/utils.tsx @@ -1,4 +1,5 @@ import { starknetKeccak } from "@dojoengine/torii-client"; +import { Buffer } from "buffer"; export const getMessageKey = (addressOne: string | bigint, addressTwo: string | bigint) => { if (typeof addressOne === "string") { diff --git a/client/apps/game/src/ui/modules/entity-details/battles.tsx b/client/apps/game/src/ui/modules/entity-details/battles.tsx index 165612f119..9dd60d5cf7 100644 --- a/client/apps/game/src/ui/modules/entity-details/battles.tsx +++ b/client/apps/game/src/ui/modules/entity-details/battles.tsx @@ -1,15 +1,14 @@ -import { BattleInfo } from "@/hooks/helpers/battles/use-battles"; import { BattleListItem } from "@/ui/components/battles/battle-list-item"; -import { ArmyInfo } from "@bibliothecadao/eternum"; +import { ArmyInfo, ID } from "@bibliothecadao/eternum"; -export const Battles = ({ ownArmy, battles }: { ownArmy: ArmyInfo | undefined; battles: BattleInfo[] }) => { +export const Battles = ({ ownArmy, battles }: { ownArmy: ArmyInfo | undefined; battles: ID[] }) => { return (
{battles.length > 0 && ( <>
Battles
{battles.map((battle) => ( - + ))} )} diff --git a/client/apps/game/src/ui/modules/entity-details/combat-entity-details.tsx b/client/apps/game/src/ui/modules/entity-details/combat-entity-details.tsx index f5334ee3f0..e02d9ba531 100644 --- a/client/apps/game/src/ui/modules/entity-details/combat-entity-details.tsx +++ b/client/apps/game/src/ui/modules/entity-details/combat-entity-details.tsx @@ -1,6 +1,5 @@ -import { useBattlesByPosition } from "@/hooks/helpers/battles/use-battles"; -import { useOwnArmiesByPosition } from "@/hooks/helpers/use-armies"; -import { useEntities } from "@/hooks/helpers/use-entities"; +import { useArmiesAtPosition } from "@/hooks/helpers/use-armies"; +import { useBattlesAtPosition } from "@/hooks/helpers/use-battles"; import { useStructureAtPosition } from "@/hooks/helpers/use-structures"; import useUIStore from "@/hooks/store/use-ui-store"; import { Position } from "@/types/position"; @@ -23,25 +22,23 @@ export const CombatEntityDetails = () => { () => new Position({ x: selectedHex?.col || 0, y: selectedHex?.row || 0 }), [selectedHex], ); - const { playerStructures } = useEntities(); - const ownArmiesAtPosition = useOwnArmiesByPosition({ + const armiesAtPosition = useArmiesAtPosition({ position: hexPosition.getContract(), - inBattle: false, - playerStructures: playerStructures(), }); - const userArmies = useMemo( - () => ownArmiesAtPosition.filter((army) => army.health.current > 0), - [ownArmiesAtPosition], + // player armies that are not in battle + const playerArmies = useMemo( + () => armiesAtPosition.filter((army) => army.isMine && army.battle_id === 0), + [armiesAtPosition], ); const ownArmy = useMemo(() => { - return ownArmiesAtPosition.find((army) => army.entity_id === selectedEntityId); - }, [ownArmiesAtPosition, selectedEntityId]); + return playerArmies.find((army) => army.entity_id === selectedEntityId); + }, [playerArmies, selectedEntityId]); const structure = useStructureAtPosition(hexPosition.getContract()); - const battles = useBattlesByPosition(hexPosition.getContract()); + const battles = useBattlesAtPosition(hexPosition.getContract()); const tabs = useMemo( () => [ @@ -52,7 +49,7 @@ export const CombatEntityDetails = () => {
Entities
), - component: selectedHex && , + component: selectedHex && , }, ...(structure ? [ @@ -85,11 +82,11 @@ export const CombatEntityDetails = () => { {tab.label} ))} - {selectedTab !== 2 && userArmies.length > 0 && ( + {selectedTab !== 2 && playerArmies.length > 0 && ( )} diff --git a/client/apps/game/src/ui/modules/entity-details/entities.tsx b/client/apps/game/src/ui/modules/entity-details/entities.tsx index 4be137ad2f..606e075b2b 100644 --- a/client/apps/game/src/ui/modules/entity-details/entities.tsx +++ b/client/apps/game/src/ui/modules/entity-details/entities.tsx @@ -1,31 +1,27 @@ -import { BattleInfo } from "@/hooks/helpers/battles/use-battles"; -import { useEnemyArmiesByPosition } from "@/hooks/helpers/use-armies"; -import { useEntities } from "@/hooks/helpers/use-entities"; +import { useArmiesAtPosition } from "@/hooks/helpers/use-armies"; import { Position } from "@/types/position"; import { StructureCard } from "@/ui/components/structures/worldmap/structure-card"; import { Checkbox } from "@/ui/elements/checkbox"; import { Battles } from "@/ui/modules/entity-details/battles"; import { EnemyArmies } from "@/ui/modules/entity-details/enemy-armies"; -import { ArmyInfo } from "@bibliothecadao/eternum"; +import { ArmyInfo, ID } from "@bibliothecadao/eternum"; import { useState } from "react"; export const Entities = ({ position, ownArmy, - battles, + battleEntityIds, }: { position: Position; ownArmy: ArmyInfo | undefined; - battles: BattleInfo[]; + battleEntityIds: ID[]; }) => { const [showStructure, setShowStructure] = useState(true); const [showBattles, setShowBattles] = useState(true); const [showArmies, setShowArmies] = useState(true); - const { playerStructures } = useEntities(); - const enemyArmies = useEnemyArmiesByPosition({ + const armiesAtPosition = useArmiesAtPosition({ position: position.getContract(), - playerStructures: playerStructures(), }); return ( @@ -36,9 +32,9 @@ export const Entities = ({ setShowArmies((prev) => !prev)} text="Show armies" />
{showStructure && } - {showBattles && } - {showArmies && enemyArmies.length > 0 && ( - + {showBattles && } + {showArmies && armiesAtPosition.length > 0 && ( + )}
); diff --git a/client/apps/game/src/ui/modules/military/battle-view/battle-actions.tsx b/client/apps/game/src/ui/modules/military/battle-view/battle-actions.tsx index 5fd3cb7afa..ee5864fc15 100644 --- a/client/apps/game/src/ui/modules/military/battle-view/battle-actions.tsx +++ b/client/apps/game/src/ui/modules/military/battle-view/battle-actions.tsx @@ -3,7 +3,7 @@ import { ReactComponent as Burn } from "@/assets/icons/burn.svg"; import { ReactComponent as Castle } from "@/assets/icons/castle.svg"; import { ReactComponent as Flag } from "@/assets/icons/flag.svg"; import { useDojo } from "@/hooks/context/dojo-context"; -import { getArmyByEntityId } from "@/hooks/helpers/use-armies"; +import { useGetArmyByEntityId } from "@/hooks/helpers/use-armies"; import { useModalStore } from "@/hooks/store/use-modal-store"; import useUIStore from "@/hooks/store/use-ui-store"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; @@ -67,7 +67,7 @@ export const BattleActions = ({ } = dojo; const { toggleModal } = useModalStore(); - const { getAliveArmy } = getArmyByEntityId(); + const { getArmy } = useGetArmyByEntityId(); const setTooltip = useUIStore((state) => state.setTooltip); const { nextBlockTimestamp: currentTimestamp, currentArmiesTick } = useNextBlockTimestamp(); @@ -88,7 +88,7 @@ export const BattleActions = ({ const isActive = useMemo(() => battleManager.isBattleOngoing(currentTimestamp!), [battleManager, currentTimestamp]); const selectedArmy = useMemo(() => { - return getAliveArmy(localSelectedUnit || 0); + return getArmy(localSelectedUnit || 0); }, [localSelectedUnit, isActive, userArmiesInBattle]); const defenderArmy = useMemo(() => { diff --git a/client/apps/game/src/ui/modules/military/battle-view/battle-history.tsx b/client/apps/game/src/ui/modules/military/battle-view/battle-history.tsx index 1ad2ac97f5..6d1d2ca589 100644 --- a/client/apps/game/src/ui/modules/military/battle-view/battle-history.tsx +++ b/client/apps/game/src/ui/modules/military/battle-view/battle-history.tsx @@ -1,4 +1,4 @@ -import { getArmyByEntityId } from "@/hooks/helpers/use-armies"; +import { useGetArmyByEntityId } from "@/hooks/helpers/use-armies"; import { useBattleJoin, useBattleLeave, useBattleStart } from "@/hooks/helpers/use-battle-events"; import { currencyFormat, formatTime } from "@/ui/utils/utils"; import { BattleSide, ClientComponents, ID } from "@bibliothecadao/eternum"; @@ -29,7 +29,7 @@ export const BattleHistory = ({ battleId, battleSide }: { battleId: ID; battleSi const battleJoinData = useBattleJoin(battleId, battleSide); const battleLeaveData = useBattleLeave(battleId, battleSide); - const { getArmy } = getArmyByEntityId(); + const { getArmy } = useGetArmyByEntityId(); const events = useMemo(() => { return [...battleStartData, ...battleJoinData, ...battleLeaveData].sort( diff --git a/client/apps/game/src/ui/modules/military/battle-view/battle-view.tsx b/client/apps/game/src/ui/modules/military/battle-view/battle-view.tsx index da45a3c7dd..9d5a054b0e 100644 --- a/client/apps/game/src/ui/modules/military/battle-view/battle-view.tsx +++ b/client/apps/game/src/ui/modules/military/battle-view/battle-view.tsx @@ -1,6 +1,6 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { useBattleManager } from "@/hooks/helpers/battles/use-battles"; -import { getArmiesByBattleId, getArmyByEntityId, useArmyByArmyEntityId } from "@/hooks/helpers/use-armies"; +import { useArmiesInBattle, useArmyByArmyEntityId, useGetArmyByEntityId } from "@/hooks/helpers/use-armies"; +import { useBattleManager } from "@/hooks/helpers/use-battles"; import { useStructureByEntityId, useStructureByPosition } from "@/hooks/helpers/use-structures"; import useUIStore from "@/hooks/store/use-ui-store"; import useNextBlockTimestamp from "@/hooks/use-next-block-timestamp"; @@ -11,8 +11,7 @@ import { memo, useMemo } from "react"; export const BattleView = memo(() => { const dojo = useDojo(); const getStructure = useStructureByPosition(); - const armiesByBattleId = getArmiesByBattleId(); - const { getAliveArmy } = getArmyByEntityId(); + const { getArmy } = useGetArmyByEntityId(); const { nextBlockTimestamp: currentTimestamp } = useNextBlockTimestamp(); const battleView = useUIStore((state) => state.battleView); @@ -40,49 +39,45 @@ export const BattleView = memo(() => { battleView?.battleEntityId ? battleView?.battleEntityId : battleView?.engage ? 0 : targetArmy?.battle_id || 0, ); - const armies = useMemo(() => { - if (!battleManager.isBattle()) { - return { armiesInBattle: [], userArmiesInBattle: [] }; - } - const armiesInBattle = armiesByBattleId(battleManager?.battleEntityId || 0); + const armiesInBattle = useArmiesInBattle(battleManager.battleEntityId); - const userArmiesInBattle = armiesInBattle.filter((army) => army.isMine); - return { armiesInBattle, userArmiesInBattle }; - }, [battleManager, battleView]); + const playerArmiesInBattle = useMemo(() => { + return armiesInBattle.filter((army) => army.isMine); + }, [armiesInBattle]); const ownArmySide = useMemo( () => battleManager.isBattle() - ? armies.userArmiesInBattle?.[0]?.battle_side || BattleSide[BattleSide.None] + ? playerArmiesInBattle?.[0]?.battle_side || BattleSide[BattleSide.None] : BattleSide[BattleSide.Attack], - [battleManager, armies.userArmiesInBattle], + [battleManager, playerArmiesInBattle], ); const ownArmyBattleStarter = useMemo( - () => getAliveArmy(battleView?.ownArmyEntityId || 0), + () => getArmy(battleView?.ownArmyEntityId || 0), [battleView?.ownArmyEntityId || 0], ); const attackerArmies = useMemo( () => - armies.armiesInBattle.length > 0 - ? armies.armiesInBattle.filter((army) => army.battle_side === BattleSide[BattleSide.Attack]) + armiesInBattle.length > 0 + ? armiesInBattle.filter((army) => army.battle_side === BattleSide[BattleSide.Attack]) : [ownArmyBattleStarter!], - [armies.armiesInBattle, ownArmyBattleStarter], + [armiesInBattle, ownArmyBattleStarter], ); const defenderArmies = useMemo( () => - armies.armiesInBattle.length > 0 - ? armies.armiesInBattle.filter((army) => army.battle_side === BattleSide[BattleSide.Defence]) + armiesInBattle.length > 0 + ? armiesInBattle.filter((army) => army.battle_side === BattleSide[BattleSide.Defence]) : [targetArmy], - [armies.armiesInBattle, targetArmy], + [armiesInBattle, targetArmy], ); const battleAdjusted = useMemo(() => { if (!battleManager) return undefined; return battleManager!.getUpdatedBattle(currentTimestamp!); - }, [currentTimestamp, battleManager, battleManager?.battleEntityId, armies.armiesInBattle, battleView]); + }, [currentTimestamp, battleManager, battleManager?.battleEntityId, battleView]); const attackerHealth = useMemo(() => { if (battleAdjusted) { @@ -147,7 +142,7 @@ export const BattleView = memo(() => { defenderArmies={defenderArmies} defenderHealth={defenderHealth} defenderTroops={defenderTroops} - userArmiesInBattle={armies.userArmiesInBattle} + userArmiesInBattle={playerArmiesInBattle} structure={structure as Structure} /> ); diff --git a/client/apps/game/src/ui/modules/world-structures/world-structures-menu.tsx b/client/apps/game/src/ui/modules/world-structures/world-structures-menu.tsx index fafcf9d6be..234aaa2f2d 100644 --- a/client/apps/game/src/ui/modules/world-structures/world-structures-menu.tsx +++ b/client/apps/game/src/ui/modules/world-structures/world-structures-menu.tsx @@ -1,5 +1,5 @@ import { useDojo } from "@/hooks/context/dojo-context"; -import { getArmiesByPosition } from "@/hooks/helpers/use-armies"; +import { useArmiesAtPosition } from "@/hooks/helpers/use-armies"; import { useGetHyperstructuresWithContributionsFromPlayer } from "@/hooks/helpers/use-contributions"; import { useEntitiesUtils } from "@/hooks/helpers/use-entities"; import { useFragmentMines } from "@/hooks/helpers/use-fragment-mines"; @@ -174,9 +174,8 @@ const BaseStructureExtraContent = ({ }) => { const { getGuildFromPlayerAddress } = useGuilds(); const { getAddressNameFromEntity, getPlayerAddressFromEntity } = useEntitiesUtils(); - const getArmies = getArmiesByPosition(); - const armies = useMemo(() => getArmies({ x, y }), [x, y]); + const armies = useArmiesAtPosition({ position: { x, y } }); const structureOwner = useMemo(() => { const ownerName = getAddressNameFromEntity(entityId); diff --git a/client/apps/game/src/utils/army.ts b/client/apps/game/src/utils/army.ts new file mode 100644 index 0000000000..fc6c9443fa --- /dev/null +++ b/client/apps/game/src/utils/army.ts @@ -0,0 +1,106 @@ +import { + ArmyInfo, + CapacityConfigCategory, + ClientComponents, + ContractAddress, + EternumGlobalConfig, + getArmyTotalCapacity, +} from "@bibliothecadao/eternum"; +import { Entity, getComponentValue } from "@dojoengine/recs"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import { shortString } from "starknet"; + +export const formatArmies = (armies: Entity[], playerAddress: string, components: ClientComponents): ArmyInfo[] => { + return armies + .map((armyEntityId) => { + const army = getComponentValue(components.Army, armyEntityId); + if (!army) return undefined; + + const position = getComponentValue(components.Position, armyEntityId); + if (!position) return undefined; + + const entityOwner = getComponentValue(components.EntityOwner, armyEntityId); + if (!entityOwner) return undefined; + + const owner = getComponentValue(components.Owner, getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)])); + + let health = structuredClone(getComponentValue(components.Health, armyEntityId)); + if (health) { + health.current = health.current / BigInt(EternumGlobalConfig.resources.resourcePrecision); + health.lifetime = health.lifetime / BigInt(EternumGlobalConfig.resources.resourcePrecision); + } else { + health = { + entity_id: army.entity_id, + current: 0n, + lifetime: 0n, + }; + } + const protectee = getComponentValue(components.Protectee, armyEntityId); + + let quantity = structuredClone(getComponentValue(components.Quantity, armyEntityId)); + if (quantity) { + quantity.value = BigInt(quantity.value) / BigInt(EternumGlobalConfig.resources.resourcePrecision); + } else { + quantity = { + entity_id: army.entity_id, + value: 0n, + }; + } + + const movable = getComponentValue(components.Movable, armyEntityId); + + const armyCapacityConfigEntityId = getEntityIdFromKeys([BigInt(CapacityConfigCategory.Army)]); + const capacity = getComponentValue(components.CapacityConfig, armyCapacityConfigEntityId); + const totalCapacity = capacity ? getArmyTotalCapacity(army, capacity) : 0n; + + const weightComponentValue = getComponentValue(components.Weight, armyEntityId); + const weight = weightComponentValue + ? weightComponentValue.value / BigInt(EternumGlobalConfig.resources.resourcePrecision) + : 0n; + + const arrivalTime = getComponentValue(components.ArrivalTime, armyEntityId); + const stamina = getComponentValue(components.Stamina, armyEntityId); + const name = getComponentValue(components.EntityName, armyEntityId); + const realm = + entityOwner && getComponentValue(components.Realm, getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)])); + const homePosition = + realm && getComponentValue(components.Position, getEntityIdFromKeys([BigInt(realm.entity_id)])); + + const structure = getComponentValue( + components.Structure, + getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)]), + ); + + const structurePosition = + structure && getComponentValue(components.Position, getEntityIdFromKeys([BigInt(structure.entity_id)])); + + const isMine = (owner?.address || 0n) === ContractAddress(playerAddress); + const isMercenary = owner === undefined; + + const isHome = structurePosition && position.x === structurePosition.x && position.y === structurePosition.y; + + return { + ...army, + protectee, + health, + movable, + quantity, + totalCapacity, + weight, + arrivalTime, + position, + entityOwner, + stamina, + owner, + realm, + homePosition, + isMine, + isMercenary, + isHome, + name: name + ? shortString.decodeShortString(name.name.toString()) + : `${protectee ? "🛡️" : "🗡️"}` + `Army ${army.entity_id}`, + }; + }) + .filter((army): army is ArmyInfo => army !== undefined); +}; diff --git a/client/sdk/packages/eternum/src/modelManager/BattleManager.ts b/client/sdk/packages/eternum/src/modelManager/BattleManager.ts index 6e86ac291f..71dc9a4030 100644 --- a/client/sdk/packages/eternum/src/modelManager/BattleManager.ts +++ b/client/sdk/packages/eternum/src/modelManager/BattleManager.ts @@ -3,60 +3,22 @@ import { getEntityIdFromKeys } from "@dojoengine/utils"; import { EternumGlobalConfig, MIN_TROOPS_BATTLE } from "../constants"; import { ClientComponents } from "../dojo/createClientComponents"; import { EternumProvider } from "../provider"; -import { BattleSide, Health, ID } from "../types"; +import { + BattleSide, + BattleStartStatus, + BattleStatus, + BattleType, + ClaimStatus, + Health, + ID, + LeaveStatus, + RaidStatus, +} from "../types"; import { multiplyByPrecision } from "../utils"; import { configManager } from "./ConfigManager"; import { StaminaManager } from "./StaminaManager"; import { ArmyInfo, DojoAccount, Structure } from "./types"; -export enum BattleType { - Hex, - Structure, -} - -export enum BattleStatus { - BattleStart = "Start battle", - BattleOngoing = "", - UserWon = "Victory", - UserLost = "Defeat", - BattleEnded = "Battle has ended", -} - -export enum RaidStatus { - isRaidable = "Raid!", - NoStamina = "Not enough stamina", - NoStructureToClaim = "No structure to raid", - OwnStructure = "Can't raid your own structure", - NoArmy = "No army selected", - ArmyNotInBattle = "Selected army not in this battle", - MinTroops = "Minimum 100 troops required", -} - -export enum LeaveStatus { - Leave = "Leave", - NoBattleToLeave = "No battle to leave", - DefenderCantLeaveOngoing = "A defender can't leave an ongoing battle", - NoArmyInBattle = "Your armies aren't in this battle", -} - -export enum BattleStartStatus { - MinTroops = "Minimum 100 troops required", - BattleStart = "Start battle", - ForceStart = "Force start", - NothingToAttack = "Nothing to attack", - CantStart = "Can't start a battle now.", -} - -export enum ClaimStatus { - Claimable = "Claim", - NoSelectedArmy = "No selected army", - BattleOngoing = "Battle ongoing", - DefenderPresent = "An army's defending the structure", - NoStructureToClaim = "No structure to claim", - StructureIsMine = "Can't claim your own structure", - SelectedArmyIsDead = "Selected army is dead", -} - export class BattleManager { battleType: BattleType | undefined; @@ -66,6 +28,11 @@ export class BattleManager { public readonly battleEntityId: ID, ) {} + public getArmiesInBattle(): ID[] { + const armiesEntityIds = runQuery([HasValue(this.components.Army, { battle_id: this.battleEntityId })]); + return Array.from(armiesEntityIds).map((entityId) => getComponentValue(this.components.Army, entityId)!.entity_id); + } + public getUpdatedBattle(currentTimestamp: number) { const battle = this.getBattle(); if (!battle) return; @@ -167,13 +134,6 @@ export class BattleManager { return date; } - // todo: used deleteEntity directly in the react app, check if works there - // public deleteBattle() { - // removeComponent(this.components.Battle, getEntityIdFromKeys([BigInt(this.battleEntityId)])); - // this.dojo.network.world.deleteEntity(getEntityIdFromKeys([BigInt(this.battleEntityId)])); - // this.battleEntityId = 0; - // } - public isBattleOngoing(currentTimestamp: number): boolean { const battle = this.getBattle(); @@ -190,6 +150,10 @@ export class BattleManager { return getComponentValue(this.components.Battle, getEntityIdFromKeys([BigInt(this.battleEntityId)])); } + public getPosition(): ComponentValue | undefined { + return getComponentValue(this.components.Position, getEntityIdFromKeys([BigInt(this.battleEntityId)])); + } + public getUpdatedArmy( army: ArmyInfo | undefined, updatedBattle: ComponentValue | undefined, @@ -362,23 +326,6 @@ export class BattleManager { ); } - public async pillageStructure(signer: DojoAccount, raider: ArmyInfo, structureEntityId: ID) { - if (this.battleEntityId !== 0 && this.battleEntityId === raider.battle_id) { - await this.provider.battle_leave_and_pillage({ - signer, - army_id: raider.entity_id, - battle_id: this.battleEntityId, - structure_id: structureEntityId, - }); - } else { - await this.provider.battle_pillage({ - signer, - army_id: raider.entity_id, - structure_id: structureEntityId, - }); - } - } - public getBattleType(structure: Structure | undefined): BattleType { if (this.battleType) return this.battleType; @@ -391,6 +338,13 @@ export class BattleManager { return this.battleType; } + public getEscrowIds(): { attacker: ID; defender: ID } { + const battle = this.getBattle(); + if (!battle) return { attacker: 0, defender: 0 }; + + return { attacker: battle.attackers_resources_escrow_id, defender: battle.defenders_resources_escrow_id }; + } + public getWinner(currentTimestamp: number, ownArmySide: string): BattleStatus { const battle = this.getUpdatedBattle(currentTimestamp); if (!battle) return BattleStatus.BattleStart; @@ -519,4 +473,21 @@ export class BattleManager { if (lifetime_troops === 0n) return 0; return Number(current_troops) / Number(lifetime_troops); } + + public async pillageStructure(signer: DojoAccount, raider: ArmyInfo, structureEntityId: ID) { + if (this.battleEntityId !== 0 && this.battleEntityId === raider.battle_id) { + await this.provider.battle_leave_and_pillage({ + signer, + army_id: raider.entity_id, + battle_id: this.battleEntityId, + structure_id: structureEntityId, + }); + } else { + await this.provider.battle_pillage({ + signer, + army_id: raider.entity_id, + structure_id: structureEntityId, + }); + } + } } diff --git a/client/sdk/packages/eternum/src/modelManager/types/index.ts b/client/sdk/packages/eternum/src/modelManager/types/index.ts index 6536797370..4e36c6940c 100644 --- a/client/sdk/packages/eternum/src/modelManager/types/index.ts +++ b/client/sdk/packages/eternum/src/modelManager/types/index.ts @@ -5,6 +5,11 @@ import { Position } from "../../types"; export type DojoAccount = Account | AccountInterface; +export type BattleInfo = ComponentValue & { + isStructureBattle: boolean; + position: ComponentValue; +}; + export type ArmyInfo = ComponentValue & { name: string; isMine: boolean; diff --git a/client/sdk/packages/eternum/src/types/common.ts b/client/sdk/packages/eternum/src/types/common.ts index c2baffbf63..19711d842d 100644 --- a/client/sdk/packages/eternum/src/types/common.ts +++ b/client/sdk/packages/eternum/src/types/common.ts @@ -8,6 +8,54 @@ import { TroopFoodConsumption, } from "../constants"; +export enum BattleType { + Hex, + Structure, +} + +export enum BattleStatus { + BattleStart = "Start battle", + BattleOngoing = "", + UserWon = "Victory", + UserLost = "Defeat", + BattleEnded = "Battle has ended", +} + +export enum RaidStatus { + isRaidable = "Raid!", + NoStamina = "Not enough stamina", + NoStructureToClaim = "No structure to raid", + OwnStructure = "Can't raid your own structure", + NoArmy = "No army selected", + ArmyNotInBattle = "Selected army not in this battle", + MinTroops = "Minimum 100 troops required", +} + +export enum LeaveStatus { + Leave = "Leave", + NoBattleToLeave = "No battle to leave", + DefenderCantLeaveOngoing = "A defender can't leave an ongoing battle", + NoArmyInBattle = "Your armies aren't in this battle", +} + +export enum BattleStartStatus { + MinTroops = "Minimum 100 troops required", + BattleStart = "Start battle", + ForceStart = "Force start", + NothingToAttack = "Nothing to attack", + CantStart = "Can't start a battle now.", +} + +export enum ClaimStatus { + Claimable = "Claim", + NoSelectedArmy = "No selected army", + BattleOngoing = "Battle ongoing", + DefenderPresent = "An army's defending the structure", + NoStructureToClaim = "No structure to claim", + StructureIsMine = "Can't claim your own structure", + SelectedArmyIsDead = "Selected army is dead", +} + export type HexPosition = { col: number; row: number }; export enum Winner {