From 6773351360c0e5c495f45936f61f1302834f906c Mon Sep 17 00:00:00 2001 From: tedison <76473430+edisontim@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:57:19 +0200 Subject: [PATCH] ref: combat (#1120) * Heavy refactor + addition of a testing framework * Add CI * scarb fmt --- .github/workflows/test-client.yml | 36 ++ .../{test.yml => test-contracts.yml} | 0 client/package.json | 22 +- client/src/dojo/createClientComponents.ts | 1 - client/src/dojo/modelManager/BattleManager.ts | 167 +++-- .../src/dojo/modelManager/BuildingManager.ts | 95 --- client/src/dojo/modelManager/MarketManager.ts | 18 +- .../dojo/modelManager/ProductionManager.ts | 36 +- client/src/dojo/modelManager/types.ts | 90 --- .../helpers/battles/__test__/__mock__.tsx | 54 ++ .../battles/__test__/useBattles.test.tsx | 86 +++ .../battles/__test__/useBattlesUtils.test.tsx | 161 +++++ .../helpers/{ => battles}/useBattles.tsx | 151 +++-- .../hooks/helpers/battles/useBattlesUtils.tsx | 43 ++ client/src/hooks/helpers/useArmies.tsx | 253 ++++---- client/src/hooks/helpers/useBanks.tsx | 6 +- client/src/hooks/helpers/useBuildings.tsx | 10 +- client/src/hooks/helpers/useCaravans.tsx | 3 +- client/src/hooks/helpers/useEntities.tsx | 138 ++-- client/src/hooks/helpers/useGuilds.tsx | 12 +- client/src/hooks/helpers/useHexPosition.tsx | 8 +- .../src/hooks/helpers/useHyperstructures.tsx | 20 +- client/src/hooks/helpers/useLevel.tsx | 117 ---- client/src/hooks/helpers/useRealm.tsx | 2 +- client/src/hooks/helpers/useResources.tsx | 6 +- client/src/hooks/helpers/useRoads.tsx | 10 +- client/src/hooks/helpers/useStamina.tsx | 10 +- client/src/hooks/helpers/useStructures.tsx | 100 ++- client/src/hooks/helpers/useTrade.tsx | 18 +- client/src/hooks/helpers/useTravel.tsx | 10 +- client/src/hooks/store/_mapStore.tsx | 4 +- client/src/hooks/store/useBlockchainStore.tsx | 4 +- client/src/hooks/store/useQuestStore.tsx | 2 +- client/src/hooks/useUISound.tsx | 2 - client/src/setupTests.ts | 0 .../cityview/realm/RealmInfoComponent.tsx | 187 ------ .../cityview/realm/leveling/Leveling.tsx | 134 ---- .../cityview/realm/leveling/LevelingPopup.tsx | 306 --------- .../components/cityview/realm/leveling/trace | 28 - .../construction/ExistingBuildings.tsx | 1 - .../ui/components/construction/GroundGrid.tsx | 18 +- client/src/ui/components/entities/Entity.tsx | 38 +- .../entities/SelectLocationPanel.tsx | 28 +- .../hyperstructures/StructureCard.tsx | 14 +- .../src/ui/components/military/ArmyChip.tsx | 75 ++- .../src/ui/components/military/ArmyList.tsx | 27 +- .../military/ArmyManagementCard.tsx | 95 ++- .../src/ui/components/military/ArmyPanel.tsx | 3 +- .../ui/components/military/ArmyViewCard.tsx | 8 +- client/src/ui/components/military/Battle.tsx | 9 +- .../components/military/BattlesArmyTable.tsx | 2 +- .../src/ui/components/military/TroopChip.tsx | 10 +- .../components/models/biomes/BeachBiome.tsx | 5 +- .../models/biomes/DeciduousForestBiome.tsx | 5 +- .../models/biomes/DeepOceanBiome.tsx | 5 +- .../components/models/biomes/DesertBiome.tsx | 5 +- .../models/biomes/GrasslandBiome.tsx | 5 +- .../ui/components/models/biomes/HexGrid.tsx | 5 +- .../components/models/biomes/OceanBiome.tsx | 5 +- .../models/biomes/ScorchedBiome.tsx | 5 +- .../models/biomes/ShrublandBiome.tsx | 5 +- .../ui/components/models/biomes/SnowBiome.tsx | 5 +- .../models/biomes/SubtropicalDesertBiome.tsx | 5 +- .../components/models/biomes/TaigaBiome.tsx | 5 +- .../models/biomes/TemperateDesertBiome.tsx | 5 +- .../biomes/TemperateRainforestBiome.tsx | 5 +- .../models/biomes/TropicalRainforestBiome.tsx | 5 +- .../biomes/TropicalSeasonalForestBiome.tsx | 5 +- .../components/models/biomes/TundraBiome.tsx | 5 +- .../models/buildings/worldmap/Battles.tsx | 9 +- .../buildings/worldmap/InstancedCastles.tsx | 12 +- .../models/buildings/worldmap/ShardsMines.tsx | 13 +- .../components/resources/DepositResources.tsx | 2 +- .../resources/InventoryResources.tsx | 25 +- .../components/trading/TradeHistoryEvent.tsx | 6 +- .../ui/components/worldmap/armies/Armies.tsx | 18 +- .../ui/components/worldmap/armies/Army.tsx | 35 +- .../components/worldmap/armies/ArmyHitBox.tsx | 16 +- .../worldmap/armies/ArmyInfoLabel.tsx | 28 +- .../worldmap/armies/BattleLabel.tsx | 2 +- .../ui/components/worldmap/armies/utils.tsx | 9 +- .../hexagon/HexagonInformationPanel.tsx | 190 ------ .../worldmap/realms/RealmListItem.tsx | 13 +- .../worldmap/structures/StructureListItem.tsx | 2 +- client/src/ui/elements/RealmLevel.tsx | 12 - .../modules/entity-details/EntityDetails.tsx | 188 +++++- client/src/ui/modules/military/Military.tsx | 48 +- .../modules/military/battle-view/Battle.tsx | 15 +- .../military/battle-view/BattleActions.tsx | 27 +- .../military/battle-view/BattleDetails.tsx | 4 +- .../battle-view/BattleProgressBar.tsx | 13 +- .../military/battle-view/BattleView.tsx | 20 +- .../navigation/LeftNavigationModule.tsx | 8 +- .../navigation/RightNavigationModule.tsx | 4 +- .../navigation/TopMiddleNavigation.tsx | 12 +- .../ui/modules/scenes/HexceptionViewScene.tsx | 16 +- .../world-structures/WorldStructuresMenu.tsx | 8 +- client/tailwind.config.js | 12 + client/vitest.config.ts | 18 + contracts/src/systems/combat/tests.cairo | 104 ++- pnpm-lock.yaml | 608 ++++++++++++++++-- sdk/packages/eternum/src/constants/global.ts | 2 +- 102 files changed, 2221 insertions(+), 2041 deletions(-) create mode 100644 .github/workflows/test-client.yml rename .github/workflows/{test.yml => test-contracts.yml} (100%) delete mode 100644 client/src/dojo/modelManager/BuildingManager.ts delete mode 100644 client/src/dojo/modelManager/types.ts create mode 100644 client/src/hooks/helpers/battles/__test__/__mock__.tsx create mode 100644 client/src/hooks/helpers/battles/__test__/useBattles.test.tsx create mode 100644 client/src/hooks/helpers/battles/__test__/useBattlesUtils.test.tsx rename client/src/hooks/helpers/{ => battles}/useBattles.tsx (51%) create mode 100644 client/src/hooks/helpers/battles/useBattlesUtils.tsx delete mode 100644 client/src/hooks/helpers/useLevel.tsx create mode 100644 client/src/setupTests.ts delete mode 100644 client/src/ui/components/cityview/realm/RealmInfoComponent.tsx delete mode 100644 client/src/ui/components/cityview/realm/leveling/Leveling.tsx delete mode 100644 client/src/ui/components/cityview/realm/leveling/LevelingPopup.tsx delete mode 100644 client/src/ui/components/cityview/realm/leveling/trace delete mode 100644 client/src/ui/components/worldmap/hexagon/HexagonInformationPanel.tsx delete mode 100644 client/src/ui/elements/RealmLevel.tsx create mode 100644 client/vitest.config.ts diff --git a/.github/workflows/test-client.yml b/.github/workflows/test-client.yml new file mode 100644 index 000000000..79a09e293 --- /dev/null +++ b/.github/workflows/test-client.yml @@ -0,0 +1,36 @@ +name: client + +on: + push: + branches: + - main + pull_request: {} + +jobs: + test-client: + runs-on: ubuntu-latest + env: + VITE_PUBLIC_MASTER_ADDRESS: "0x0" + VITE_PUBLIC_MASTER_PRIVATE_KEY: "0x0" + VITE_PUBLIC_ACCOUNT_CLASS_HASH: "0x0" + + strategy: + matrix: + node-version: [21.x] + + steps: + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - uses: pnpm/action-setup@v4 + with: + version: 9.2.0 + + - uses: actions/checkout@v4 + - name: Install dependencies + run: pnpm i + - name: Build packages + run: pnpm run build-packages + - name: Execute Unit tests + run: cd client && pnpm run test diff --git a/.github/workflows/test.yml b/.github/workflows/test-contracts.yml similarity index 100% rename from .github/workflows/test.yml rename to .github/workflows/test-contracts.yml diff --git a/client/package.json b/client/package.json index 8d24ab052..bf9399513 100644 --- a/client/package.json +++ b/client/package.json @@ -6,7 +6,11 @@ "dev": "vite --host 0.0.0.0", "build": "tsc && vite build", "preview": "vite preview", - "components": "npx @dojoengine/core ../contracts/manifests/dev/manifest.json src/dojo/contractComponents.ts http://localhost:5050 0x177a3f3d912cf4b55f0f74eccf3b7def7c6144efeba033e9f21d9cdb0230c64" + "components": "npx @dojoengine/core ../contracts/manifests/dev/manifest.json src/dojo/contractComponents.ts http://localhost:5050 0x161b08e252b353008665e85ab5dcb0044a61186eb14b999657d14c04c94c824", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "coverage": "vitest run --coverage" }, "peerDependencies": { "starknet": "6.7.0" @@ -30,6 +34,7 @@ "@react-three/fiber": "^8.16.1", "@react-three/postprocessing": "2.16.2", "@reactour/tour": "^3.6.1", + "@testing-library/react-hooks": "^8.0.1", "@vercel/analytics": "^1.2.2", "@web3mq/client": "^1.0.25", "buffer": "^6.0.3", @@ -44,6 +49,7 @@ "lodash": "^4.17.21", "lucide-react": "^0.365.0", "postprocessing": "^6.35.2", + "prop-types": "^15.8.1", "react": "^18.2.0", "react-blurhash": "^0.3.0", "react-dom": "^18.2.0", @@ -63,17 +69,17 @@ }, "devDependencies": { "@svgr/rollup": "^8.1.0", - "vite-plugin-top-level-await": "^1.4.1", - "vite-plugin-wasm": "^3.3.0", "@tailwindcss/typography": "^0.5.13", + "@testing-library/react": "^16.0.0", "@types/lodash": "^4.14.202", + "@types/node": "^20.11.10", "@types/react": "^18.2.74", "@types/react-dom": "^18.2.21", - "@types/three": "^0.163.0", - "@types/node": "^20.11.10", "@types/react-resizable": "^3.0.7", + "@types/three": "^0.163.0", "@typescript-eslint/eslint-plugin": "^7.5.0", "@vitejs/plugin-react": "^4.2.1", + "@vitest/ui": "^2.0.1", "autoprefixer": "^10.4.18", "eslint": "^8.57.0", "eslint-config-standard-with-typescript": "^43.0.1", @@ -81,12 +87,16 @@ "eslint-plugin-n": "^17.0.0", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.34.1", + "jsdom": "^24.1.0", "leva": "^0.9.35", "postcss": "^8.4.35", "r3f-perf": "^7.2.1", "tailwindcss": "^3.4.1", "typescript": "^5.4.4", "vite": "^5.2.8", - "vite-plugin-svgr": "^4.2.0" + "vite-plugin-svgr": "^4.2.0", + "vite-plugin-top-level-await": "^1.4.1", + "vite-plugin-wasm": "^3.3.0", + "vitest": "^2.0.1" } } diff --git a/client/src/dojo/createClientComponents.ts b/client/src/dojo/createClientComponents.ts index ec7aab593..f5de5ad49 100644 --- a/client/src/dojo/createClientComponents.ts +++ b/client/src/dojo/createClientComponents.ts @@ -1,6 +1,5 @@ import { overridableComponent } from "@dojoengine/recs"; import { SetupNetworkResult } from "./setupNetwork"; -import { Position } from "@/ui/elements/BaseThreeTooltip"; export type ClientComponents = ReturnType; diff --git a/client/src/dojo/modelManager/BattleManager.ts b/client/src/dojo/modelManager/BattleManager.ts index d5c5acae8..413a2b251 100644 --- a/client/src/dojo/modelManager/BattleManager.ts +++ b/client/src/dojo/modelManager/BattleManager.ts @@ -1,39 +1,25 @@ -import { Component, ComponentValue, OverridableComponent, Type, getComponentValue } from "@dojoengine/recs"; +import { ArmyInfo } from "@/hooks/helpers/useArmies"; +import { BattleSide, EternumGlobalConfig, Troops } from "@bibliothecadao/eternum"; +import { Component, ComponentValue, Components, getComponentValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; -import { BattleType } from "./types"; +import { ClientComponents } from "../createClientComponents"; export class BattleManager { - battleModel: Component | OverridableComponent; battleId: bigint; + battleModel: Component; - constructor(battleModel: Component | OverridableComponent, battleId: bigint) { - this.battleModel = battleModel; + constructor(battleId: bigint, battleModel: Component) { this.battleId = battleId; + this.battleModel = battleModel; } - public getUpdatedBattle(currentTick: number) { + public getUpdatedBattle(currentTimestamp: number) { const battle = this.getBattle(); if (!battle) return; const battleClone = structuredClone(battle); - const durationPassed: number = this.getElapsedTime(currentTick); - - const attackDelta = this.attackingDelta(); - const defenceDelta = this.defendingDelta(); - - const damagesDoneToAttack = this.damagesDone(defenceDelta, durationPassed); - const damagesDoneToDefence = this.damagesDone(attackDelta, durationPassed); - - battleClone.attack_army_health.current = - damagesDoneToAttack > BigInt(battleClone.attack_army_health.current) - ? 0n - : BigInt(battleClone.attack_army_health.current) - damagesDoneToAttack; - - battleClone.defence_army_health.current = - damagesDoneToDefence > BigInt(battleClone.defence_army_health.current) - ? 0n - : BigInt(battleClone.defence_army_health.current) - damagesDoneToDefence; + this.updateHealth(battleClone, currentTimestamp); battleClone.defence_army.troops = this.getUpdatedTroops( battleClone.defence_army_health, @@ -47,10 +33,10 @@ export class BattleManager { return battleClone; } - public getElapsedTime(currentTick: number): number { + public getElapsedTime(currentTimestamp: number): number { const battle = this.getBattle(); if (!battle) return 0; - const duractionSinceLastUpdate = currentTick - Number(battle.last_updated); + const duractionSinceLastUpdate = currentTimestamp - Number(battle.last_updated); if (Number(battle.duration_left) >= duractionSinceLastUpdate) { return duractionSinceLastUpdate; } else { @@ -74,26 +60,90 @@ export class BattleManager { } } - public isBattleActive(currentTick: number): boolean { + public isBattleActive(currentTimestamp: number): boolean { const battle = this.getBattle(); - const timeSinceLastUpdate = this.getElapsedTime(currentTick); + const timeSinceLastUpdate = this.getElapsedTime(currentTimestamp); return battle ? timeSinceLastUpdate < battle.duration_left : false; } - public getBattle() { + private getBattle(): ComponentValue | undefined { return getComponentValue(this.battleModel, getEntityIdFromKeys([this.battleId])); } + public getUpdatedArmy(army: ArmyInfo, battle?: ComponentValue) { + if (BigInt(army.battle_id) !== this.battleId) { + throw new Error("Army is not in the battle"); + } + if (!battle) return army; + + const cloneArmy = structuredClone(army); + + let battle_army, battle_army_lifetime; + if (String(army.battle_side) === BattleSide[BattleSide.Defence]) { + battle_army = battle.defence_army; + battle_army_lifetime = battle.defence_army_lifetime; + } else { + battle_army = battle.attack_army; + battle_army_lifetime = battle.attack_army_lifetime; + } + + cloneArmy.health.current = this.getTroopFullHealth(battle_army.troops); + + cloneArmy.troops.knight_count = + cloneArmy.troops.knight_count === 0n + ? 0n + : BigInt( + Math.floor( + Number( + (cloneArmy.troops.knight_count * battle_army.troops.knight_count) / + battle_army_lifetime.troops.knight_count, + ), + ), + ); + + cloneArmy.troops.paladin_count = + cloneArmy.troops.paladin_count === 0n + ? 0n + : BigInt( + Math.floor( + Number( + (cloneArmy.troops.paladin_count * battle_army.troops.paladin_count) / + battle_army_lifetime.troops.paladin_count, + ), + ), + ); + + cloneArmy.troops.crossbowman_count = + cloneArmy.troops.crossbowman_count === 0n + ? 0n + : BigInt( + Math.floor( + Number( + (cloneArmy.troops.crossbowman_count * battle_army.troops.crossbowman_count) / + battle_army_lifetime.troops.crossbowman_count, + ), + ), + ); + return cloneArmy; + } + + private getTroopFullHealth(troops: Troops): bigint { + const health = EternumGlobalConfig.troop.health; + let total_knight_health = health * Number(troops.knight_count); + let total_paladin_health = health * Number(troops.paladin_count); + let total_crossbowman_health = health * Number(troops.crossbowman_count); + return BigInt( + Math.floor( + (total_knight_health + total_paladin_health + total_crossbowman_health) / + (EternumGlobalConfig.resources.resourceMultiplier * Number(EternumGlobalConfig.troop.healthPrecision)), + ), + ); + } + private getUpdatedTroops = ( health: { current: bigint; lifetime: bigint }, - currentTroops: ComponentValue< - { knight_count: Type.BigInt; paladin_count: Type.BigInt; crossbowman_count: Type.BigInt }, - unknown - >, - ): ComponentValue< - { knight_count: Type.BigInt; paladin_count: Type.BigInt; crossbowman_count: Type.BigInt }, - unknown - > => { + currentTroops: { knight_count: bigint; paladin_count: bigint; crossbowman_count: bigint }, + ): { knight_count: bigint; paladin_count: bigint; crossbowman_count: bigint } => { if (health.lifetime === 0n) { return { knight_count: 0n, @@ -101,24 +151,51 @@ export class BattleManager { crossbowman_count: 0n, }; } + return { - knight_count: (BigInt(health.current) * BigInt(currentTroops.knight_count)) / BigInt(health.lifetime), - paladin_count: (BigInt(health.current) * BigInt(currentTroops.paladin_count)) / BigInt(health.lifetime), - crossbowman_count: (BigInt(health.current) * BigInt(currentTroops.crossbowman_count)) / BigInt(health.lifetime), + knight_count: (health.current * currentTroops.knight_count) / health.lifetime, + paladin_count: (health.current * currentTroops.paladin_count) / health.lifetime, + crossbowman_count: (health.current * currentTroops.crossbowman_count) / health.lifetime, }; }; - private attackingDelta() { - const battle = this.getBattle(); - return battle ? battle.attack_delta : 0n; + private updateHealth(battle: ComponentValue, currentTimestamp: number) { + const durationPassed: number = this.getElapsedTime(currentTimestamp); + + const attackDelta = this.attackingDelta(battle); + const defenceDelta = this.defendingDelta(battle); + + battle.attack_army_health.current = this.getUdpdatedHealth(attackDelta, battle.attack_army_health, durationPassed); + battle.defence_army_health.current = this.getUdpdatedHealth( + defenceDelta, + battle.defence_army_health, + durationPassed, + ); } - private defendingDelta() { - const battle = this.getBattle(); - return battle ? battle.defence_delta : 0n; + private getUdpdatedHealth( + delta: bigint, + health: ComponentValue, + durationPassed: number, + ) { + const damagesDone = this.damagesDone(delta, durationPassed); + const currentHealthAfterDamage = this.getCurrentHealthAfterDamage(health, damagesDone); + return currentHealthAfterDamage; + } + + private attackingDelta(battle: ComponentValue) { + return battle.attack_delta; + } + + private defendingDelta(battle: ComponentValue) { + return battle.defence_delta; } private damagesDone(delta: bigint, durationPassed: number): bigint { return delta * BigInt(durationPassed); } + + private getCurrentHealthAfterDamage(health: ComponentValue, damages: bigint) { + return damages > health.current ? 0n : health.current - damages; + } } diff --git a/client/src/dojo/modelManager/BuildingManager.ts b/client/src/dojo/modelManager/BuildingManager.ts deleted file mode 100644 index df6cba231..000000000 --- a/client/src/dojo/modelManager/BuildingManager.ts +++ /dev/null @@ -1,95 +0,0 @@ -// import { Component, OverridableComponent, getComponentValue } from "@dojoengine/recs"; -// import { getEntityIdFromKeys } from "../../ui/utils/utils"; -// import { BuildingType } from "./types"; - -// enum BuildingCategory { -// None, -// Castle, -// Resource, -// Farm, -// FishingVillage, -// Barracks, -// Market, -// ArcheryRange, -// Stable, -// } - -// const ResourceTypes = { -// WHEAT: 1, -// FISH: 2, -// KNIGHT: 3, -// CROSSBOWMAN: 4, -// PALADIN: 5, -// }; - -// export class BuildingManager { -// buildingModel: Component | OverridableComponent; -// outer_col: bigint; -// outer_row: bigint; -// inner_col: bigint; -// inner_row: bigint; - -// constructor( -// buildingModel: Component | OverridableComponent, -// outer_col: bigint, -// outer_row: bigint, -// inner_col: bigint, -// inner_row: bigint, -// ) { -// this.buildingModel = buildingModel; -// this.outer_col = outer_col; -// this.outer_row = outer_row; -// this.inner_col = inner_col; -// this.inner_row = inner_row; -// } - -// public getBuilding() { -// return getComponentValue( -// this.buildingModel, -// getEntityIdFromKeys([this.outer_col, this.outer_row, this.inner_col, this.inner_row]), -// ); -// } - -// public privategetProducedResource() { -// const building = this.getBuilding(); -// if (!building) return 0; - -// switch (building.category) { -// case BuildingCategory.Resource: -// return building.produced_resource_type; -// case BuildingCategory.Farm: -// return ResourceTypes.WHEAT; -// case BuildingCategory.FishingVillage: -// return ResourceTypes.FISH; -// case BuildingCategory.Barracks: -// return ResourceTypes.KNIGHT; -// case BuildingCategory.ArcheryRange: -// return ResourceTypes.CROSSBOWMAN; -// case BuildingCategory.Stable: -// return ResourceTypes.PALADIN; -// default: -// return 0; -// } -// } - -// public isResourceProducer() { -// const producedResource = this.getProducedResource(); -// return producedResource !== 0; -// } - -// public isProductionMultiplier() { -// return !(this.getProductionBoost() !== 0); -// } - -// public getProductionBoost() { -// const building = this.getBuilding(); -// if (!building) return 0; - -// switch (building.category) { -// case BuildingCategory.Farm: -// return 0.1; -// default: -// return 0; -// } -// } -// } diff --git a/client/src/dojo/modelManager/MarketManager.ts b/client/src/dojo/modelManager/MarketManager.ts index c3290b026..7091dae00 100644 --- a/client/src/dojo/modelManager/MarketManager.ts +++ b/client/src/dojo/modelManager/MarketManager.ts @@ -1,18 +1,26 @@ import { Component, OverridableComponent, getComponentValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@/ui/utils/utils"; -import { LiquidityType, MarketType } from "./types"; import { EternumGlobalConfig } from "@bibliothecadao/eternum"; +import { ClientComponents } from "../createClientComponents"; export class MarketManager { - marketModel: Component | OverridableComponent; - liquidityModel: Component | OverridableComponent; + marketModel: + | Component + | OverridableComponent; + liquidityModel: + | Component + | OverridableComponent; bankEntityId: bigint; player: bigint; resourceId: bigint; constructor( - marketModel: Component | OverridableComponent, - liquidityModel: Component | OverridableComponent, + marketModel: + | Component + | OverridableComponent, + liquidityModel: + | Component + | OverridableComponent, bankEntityId: bigint, player: bigint, resourceId: bigint, diff --git a/client/src/dojo/modelManager/ProductionManager.ts b/client/src/dojo/modelManager/ProductionManager.ts index 6ad362fdf..acbd16f9e 100644 --- a/client/src/dojo/modelManager/ProductionManager.ts +++ b/client/src/dojo/modelManager/ProductionManager.ts @@ -1,20 +1,36 @@ -import { Component, OverridableComponent, getComponentValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@/ui/utils/utils"; -import { BuildQuantityType, ProductionType, ResourceType } from "./types"; -import { BuildingType, RESOURCE_INPUTS_SCALED, STOREHOUSE_CAPACITY } from "@bibliothecadao/eternum"; -import { EternumGlobalConfig } from "@bibliothecadao/eternum"; +import { + BuildingType, + EternumGlobalConfig, + RESOURCE_INPUTS_SCALED, + STOREHOUSE_CAPACITY, +} from "@bibliothecadao/eternum"; +import { Component, OverridableComponent, getComponentValue } from "@dojoengine/recs"; +import { ClientComponents } from "../createClientComponents"; export class ProductionManager { - productionModel: Component | OverridableComponent; - resourceModel: Component | OverridableComponent; - buildingQuantity: Component | OverridableComponent; + productionModel: + | Component + | OverridableComponent; + resourceModel: + | Component + | OverridableComponent; + buildingQuantity: + | Component + | OverridableComponent; entityId: bigint; resourceId: bigint; constructor( - productionModel: Component | OverridableComponent, - resourceModel: Component | OverridableComponent, - buildingQuantity: Component | OverridableComponent, + productionModel: + | Component + | OverridableComponent, + resourceModel: + | Component + | OverridableComponent, + buildingQuantity: + | Component + | OverridableComponent, entityId: bigint, resourceId: bigint, ) { diff --git a/client/src/dojo/modelManager/types.ts b/client/src/dojo/modelManager/types.ts deleted file mode 100644 index 2b81eb92a..000000000 --- a/client/src/dojo/modelManager/types.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Type as RecsType } from "@dojoengine/recs"; - -export type LiquidityType = { - bank_entity_id: RecsType.BigInt; - player: RecsType.BigInt; - resource_type: RecsType.BigInt; - shares: { - mag: RecsType.BigInt; - sign: RecsType.Boolean; - }; -}; - -export type MarketType = { - bank_entity_id: RecsType.BigInt; - resource_type: RecsType.BigInt; - lords_amount: RecsType.BigInt; - resource_amount: RecsType.BigInt; - total_shares: { - mag: RecsType.BigInt; - sign: RecsType.Boolean; - }; -}; - -export type ProductionType = { - entity_id: RecsType.BigInt; - resource_type: RecsType.Number; - building_count: RecsType.BigInt; - production_rate: RecsType.BigInt; - consumption_rate: RecsType.BigInt; - last_updated_tick: RecsType.BigInt; - input_finish_tick: RecsType.BigInt; -}; - -export type ResourceType = { - entity_id: RecsType.BigInt; - resource_type: RecsType.Number; - balance: RecsType.BigInt; -}; - -export type BuildQuantityType = { - entity_id: RecsType.BigInt; - category: RecsType.Number; - value: RecsType.Number; -}; - -export type BuildingType = { - outer_col: RecsType.BigInt; - outer_row: RecsType.BigInt; - inner_col: RecsType.BigInt; - inner_row: RecsType.BigInt; - category: RecsType.String; - produced_resource_type: RecsType.Number; - entity_id: RecsType.BigInt; - outer_entity_id: RecsType.BigInt; -}; - -export type QuantityTrackerType = { - entity_id: RecsType.BigInt; - count: RecsType.BigInt; -}; - -export type BattleType = { - entity_id: RecsType.BigInt; - attack_army: { - troops: { - knight_count: RecsType.BigInt; - paladin_count: RecsType.BigInt; - crossbowman_count: RecsType.BigInt; - }; - battle_id: RecsType.BigInt; - battle_side: RecsType.Number; - }; - defence_army: { - troops: { - knight_count: RecsType.BigInt; - paladin_count: RecsType.BigInt; - crossbowman_count: RecsType.BigInt; - }; - battle_id: RecsType.BigInt; - battle_side: RecsType.Number; - }; - attackers_resources_escrow_id: RecsType.BigInt; - defenders_resources_escrow_id: RecsType.BigInt; - attack_army_health: { current: RecsType.BigInt; lifetime: RecsType.BigInt }; - defence_army_health: { current: RecsType.BigInt; lifetime: RecsType.BigInt }; - attack_delta: RecsType.BigInt; - defence_delta: RecsType.BigInt; - last_updated: RecsType.BigInt; - duration_left: RecsType.BigInt; -}; diff --git a/client/src/hooks/helpers/battles/__test__/__mock__.tsx b/client/src/hooks/helpers/battles/__test__/__mock__.tsx new file mode 100644 index 000000000..cbc449442 --- /dev/null +++ b/client/src/hooks/helpers/battles/__test__/__mock__.tsx @@ -0,0 +1,54 @@ +import { BattleSide } from "@bibliothecadao/eternum"; +import { Components, ComponentValue } from "@dojoengine/recs"; +import { BattleInfo } from "../useBattles"; + +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/src/hooks/helpers/battles/__test__/useBattles.test.tsx b/client/src/hooks/helpers/battles/__test__/useBattles.test.tsx new file mode 100644 index 000000000..f2d7989e1 --- /dev/null +++ b/client/src/hooks/helpers/battles/__test__/useBattles.test.tsx @@ -0,0 +1,86 @@ +import { EternumGlobalConfig } from "@bibliothecadao/eternum"; +import { Component, Entity, getComponentValue } from "@dojoengine/recs"; +import { describe, expect, it, vi } from "vitest"; +import * as testedModule from "../useBattles"; +import { getBattle } from "../useBattles"; +import { generateMockBattle } from "./__mock__"; + +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(); + }); + + it("should return a battle for a valid battleId", () => { + const mockBattle = generateMockBattle(true, true, false); + vi.mocked(getComponentValue).mockReturnValueOnce(mockBattle); + + const expectedBattle = structuredClone(mockBattle); + const precisionFactor = + BigInt(EternumGlobalConfig.resources.resourcePrecision) * EternumGlobalConfig.troop.healthPrecision; + + 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/src/hooks/helpers/battles/__test__/useBattlesUtils.test.tsx b/client/src/hooks/helpers/battles/__test__/useBattlesUtils.test.tsx new file mode 100644 index 000000000..02def3111 --- /dev/null +++ b/client/src/hooks/helpers/battles/__test__/useBattlesUtils.test.tsx @@ -0,0 +1,161 @@ +import { BattleSide } from "@bibliothecadao/eternum"; +import { Component, Entity, runQuery } from "@dojoengine/recs"; +import { describe, expect, it, vi } from "vitest"; +import { BattleInfo, filterBattles } from "../useBattles"; +import * as testedModule from "../useBattlesUtils"; +import { generateMockArmy, generateMockBattle } from "./__mock__"; + +vi.mock("@dojoengine/recs", async () => { + const actual = await vi.importActual("@dojoengine/recs"); + return { + ...actual, + runQuery: vi.fn(), + }; +}); + +describe("protectorStillInBattle test", () => { + it("should return true for a single protector in the battle", () => { + const protectors = new Set(["0x0" as Entity]); + vi.mocked(runQuery).mockReturnValue(protectors); + + const isInBattle = testedModule.protectorStillInBattle({} as any, {} as any, {} as any); + expect(isInBattle).toBe(true); + }); + + it("should return false for no protector in the battle", () => { + const protectors = new Set([]); + vi.mocked(runQuery).mockReturnValue(protectors); + + const isInBattle = testedModule.protectorStillInBattle({} as any, {} as any, {} as any); + expect(isInBattle).toBe(false); + }); + + it("should return true for multiple protectors in the battle", () => { + const protectors = new Set(["0x1" as Entity, "0x2" as Entity]); + vi.mocked(runQuery).mockReturnValue(protectors); + + const isInBattle = testedModule.protectorStillInBattle({} as any, {} as any, {} as any); + expect(isInBattle).toBe(true); + }); +}); + +describe("armyHasLost test", () => { + it("should return false for an ongoing battle", () => { + const battle = generateMockBattle(true, true, false); + const army = generateMockArmy(BattleSide.Attack); + + const isLosingSide = testedModule.armyHasLost(army, battle); + expect(isLosingSide).toBe(false); + }); + + it("should return true for an ongoing battle", () => { + const battle = generateMockBattle(false, true, false); + const army = generateMockArmy(BattleSide.Attack); + + const isLosingSide = testedModule.armyHasLost(army, battle); + expect(isLosingSide).toBe(true); + }); + + it("should return false for an empty battle", () => { + const battle = generateMockBattle(false, true, false); + const army = generateMockArmy(BattleSide.Attack); + + const isLosingSide = testedModule.armyHasLost(army, battle); + expect(isLosingSide).toBe(true); + }); +}); + +describe("battleIsEmpty test", () => { + it("should return true when there are no attackers and defenders", () => { + const battle = generateMockBattle(true, true, false); + const Army = {} as Component; + + vi.mocked(runQuery).mockReturnValueOnce(new Set([])).mockReturnValueOnce(new Set([])); + + const isEmpty = testedModule.battleIsEmpty(Army, battle); + expect(isEmpty).toBe(true); + }); + + it("should return false when there are attackers", () => { + const battle = generateMockBattle(true, true, false); + const Army = {} as Component; + + vi.mocked(runQuery) + .mockReturnValueOnce(new Set(["0x1" as Entity])) + .mockReturnValueOnce(new Set([])); + + const isEmpty = testedModule.battleIsEmpty(Army, battle); + expect(isEmpty).toBe(false); + }); + + it("should return false when there are defenders", () => { + const battle = generateMockBattle(true, true, false); + const Army = {} as Component; + + vi.mocked(runQuery) + .mockReturnValueOnce(new Set([])) + .mockReturnValueOnce(new Set(["0x2" as Entity])); + + const isEmpty = testedModule.battleIsEmpty(Army, battle); + expect(isEmpty).toBe(false); + }); + + it("should return false when there are both attackers and defenders", () => { + const battle = generateMockBattle(true, true, false); + const Army = {} as Component; + + vi.mocked(runQuery) + .mockReturnValueOnce(new Set(["0x1" as Entity])) + .mockReturnValueOnce(new Set(["0x2" as Entity])); + + const isEmpty = testedModule.battleIsEmpty(Army, battle); + expect(isEmpty).toBe(false); + }); +}); + +describe("filterBattles", () => { + const mockArmy = {} as Component; + const mockProtectee = {} as Component; + + it("should return true when battle is not finished", () => { + const mockBattle = {} as BattleInfo; + const spy = vi.spyOn(testedModule, "battleIsFinished"); + spy.mockReturnValue(false); + + const result = filterBattles(mockBattle, mockArmy, mockProtectee); + expect(result).toBe(true); + expect(spy).toHaveBeenCalledWith(mockArmy, mockBattle); + }); + + it("should return true when battle is finished but structure battle and protector is still inside", () => { + const mockBattle = { isStructureBattle: true } as BattleInfo; + const spyBattleIsFinished = vi.spyOn(testedModule, "battleIsFinished"); + spyBattleIsFinished.mockReturnValue(true); + + const spyProtectorStillInBattle = vi.spyOn(testedModule, "protectorStillInBattle"); + spyProtectorStillInBattle.mockReturnValue(true); + + const result = filterBattles(mockBattle, mockArmy, mockProtectee); + expect(result).toBe(true); + + expect(spyBattleIsFinished).toHaveBeenCalledWith(mockArmy, mockBattle); + }); + + it("should return false when battle is finished and it's not a structure battle", () => { + const mockBattle = { isStructureBattle: false } as BattleInfo; + vi.spyOn(testedModule, "battleIsFinished").mockReturnValue(true); + vi.spyOn(testedModule, "protectorStillInBattle").mockReturnValue(true); + + const result = filterBattles(mockBattle, mockArmy, mockProtectee); + expect(result).toBe(false); + }); + + it("should return false when battle is finished, it's a structure battle and protector is not in battle", () => { + const mockBattle = { isStructureBattle: true } as BattleInfo; + vi.spyOn(testedModule, "battleIsFinished").mockReturnValue(true); + vi.spyOn(testedModule, "protectorStillInBattle").mockReturnValue(false); + + const result = filterBattles(mockBattle, mockArmy, mockProtectee); + expect(result).toBe(false); + }); +}); diff --git a/client/src/hooks/helpers/useBattles.tsx b/client/src/hooks/helpers/battles/useBattles.tsx similarity index 51% rename from client/src/hooks/helpers/useBattles.tsx rename to client/src/hooks/helpers/battles/useBattles.tsx index 943e70695..d0e0f33d3 100644 --- a/client/src/hooks/helpers/useBattles.tsx +++ b/client/src/hooks/helpers/battles/useBattles.tsx @@ -2,11 +2,23 @@ import { ClientComponents } from "@/dojo/createClientComponents"; import { BattleManager } from "@/dojo/modelManager/BattleManager"; import { EternumGlobalConfig, Position } from "@bibliothecadao/eternum"; import { useComponentValue, useEntityQuery } from "@dojoengine/react"; -import { Component, Entity, Has, HasValue, NotValue, getComponentValue, runQuery } from "@dojoengine/recs"; +import { + Component, + ComponentValue, + Components, + Entity, + Has, + HasValue, + NotValue, + getComponentValue, + runQuery, +} from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { useMemo } from "react"; import { shortString } from "starknet"; -import { useDojo } from "../context/DojoContext"; +import { useDojo } from "../../context/DojoContext"; +import * as module from "./useBattles"; +import { battleIsFinished, protectorStillInBattle } from "./useBattlesUtils"; export type FullArmyType = ClientComponents["Army"]["schema"] & ClientComponents["Health"]["schema"]; @@ -16,23 +28,39 @@ export type ExtraBattleInfo = ClientComponents["Position"]["schema"] & { opponentArmyEntityName: string; }; -export type BattleInfo = ClientComponents["Battle"]["schema"] & - ClientComponents["Position"]["schema"] & { isRealmBattle: boolean }; +export type BattleInfo = ComponentValue & + ComponentValue & { isStructureBattle: boolean }; + +export const getBattle = ( + battleId: Entity, + Battle: Component, +): ComponentValue | undefined => { + let battle = getComponentValue(Battle, battleId); + let battleClone = structuredClone(battle); + if (!battleClone) return; + const multiplier = + BigInt(EternumGlobalConfig.resources.resourcePrecision) * EternumGlobalConfig.troop.healthPrecision; + 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; +}; export const getExtraBattleInformation = ( battles: Entity[], - Battle: Component, - Position: Component, - Realm: Component, + Battle: Component, + Position: Component, + Structure: Component, ): BattleInfo[] => { return battles .map((battleEntityId) => { - const battle = getComponentValue(Battle, battleEntityId) as ClientComponents["Battle"]["schema"]; + const battle = module.getBattle(battleEntityId, Battle); if (!battle) return; - const position = getComponentValue(Position, battleEntityId) as ClientComponents["Position"]["schema"]; + const position = getComponentValue(Position, battleEntityId); if (!position) return; - const realm = runQuery([Has(Realm), HasValue(Position, { x: position.x, y: position.y })]); - return { ...battle, ...position, isRealmBattle: realm.size > 0 }; + 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)); }; @@ -44,13 +72,12 @@ export const useBattleManager = (battleId: bigint) => { }, } = useDojo(); - const battle = useComponentValue( - Battle, - getEntityIdFromKeys([BigInt(battleId)]), - ) as unknown as ClientComponents["Battle"]["schema"]; + const battle = useComponentValue(Battle, getEntityIdFromKeys([BigInt(battleId)])) as ComponentValue< + ClientComponents["Battle"]["schema"] + >; const updatedBattle = useMemo(() => { - return new BattleManager(Battle, battleId); + return new BattleManager(battleId, Battle); }, [battleId, battle]); return { updatedBattle }; @@ -59,63 +86,59 @@ export const useBattleManager = (battleId: bigint) => { export const useBattleManagerByPosition = (position: Position) => { const { setup: { - components: { Battle, Position, Army, Realm }, + components: { Battle, Position, Army, Structure, Protectee }, }, } = useDojo(); - const battleEntityIds = useEntityQuery([Has(Battle), HasValue(Position, position)]); - - const battle = getExtraBattleInformation(Array.from(battleEntityIds), Battle, Position, Realm).filter((battle) => - filterBattles(battle, Army), + const battleEntityIds = useEntityQuery([Has(Battle), HasValue(Position, { x: position.x, y: position.y })]); + const battle = getExtraBattleInformation(battleEntityIds, Battle, Position, Structure).filter((battle) => + module.filterBattles(battle, Army, Protectee), )[0]; const battleManager = useMemo(() => { if (!battle) return; - return new BattleManager(Battle, BigInt(battle.entity_id)); + return new BattleManager(BigInt(battle.entity_id), Battle); }, [position, battle]); return battleManager; }; -export const useBattles = () => { +export const useAllBattles = () => { const { setup: { - components: { Battle, Army, Position, Realm }, + components: { Battle, Army, Position, Structure, Protectee }, }, } = useDojo(); const battlesEntityIds = useEntityQuery([Has(Battle)]); - - return { - allBattles: () => - getExtraBattleInformation(battlesEntityIds, Battle, Position, Realm).filter((battle) => - filterBattles(battle, Army), - ), - }; + const allBattles = useMemo(() => { + const extraBattleInfo = getExtraBattleInformation(battlesEntityIds, Battle, Position, Structure); + return extraBattleInfo.filter((battle) => module.filterBattles(battle, Army, Protectee)); + }, [battlesEntityIds]); + return allBattles; }; export const usePlayerBattles = () => { const { account: { account }, setup: { - components: { Battle, Army, Owner, Position, Realm }, + components: { Battle, Army, Owner, Position, Structure, Protectee }, }, } = useDojo(); const armiesEntityIds = useEntityQuery([Has(Army), HasValue(Owner, { address: BigInt(account.address) })]); - const playerBattles = (): any[] => { const battleEntityIds = armiesEntityIds .map((armyEntityId: Entity) => { const ownArmy = getComponentValue(Army, armyEntityId); if (!ownArmy) return null; - const battle = getComponentValue(Battle, getEntityIdFromKeys([ownArmy.battle_id])); + const battle = module.getBattle(getEntityIdFromKeys([ownArmy.battle_id]), Battle); if (!battle) return null; return getEntityIdFromKeys([battle.entity_id]); }) .filter((entityId): entityId is Entity => entityId !== null); - return getExtraBattleInformation(battleEntityIds, Battle, Position, Realm).filter((battle) => - filterBattles(battle, Army), + return getExtraBattleInformation(battleEntityIds, Battle, Position, Structure).filter((battle) => + module.filterBattles(battle, Army, Protectee), ); }; return { playerBattles }; @@ -127,10 +150,9 @@ export const getBattleInfoByOwnArmyEntityId = (ownArmyEntityId: bigint): ExtraBa components: { Army, Battle, Position, EntityName }, }, } = useDojo(); - const ownArmy = getComponentValue(Army, getEntityIdFromKeys([ownArmyEntityId])); if (!ownArmy) return; - const battle = getComponentValue(Battle, getEntityIdFromKeys([ownArmy.battle_id])); + const battle = module.getBattle(getEntityIdFromKeys([ownArmy.battle_id]), Battle); if (!battle) return; const position = getComponentValue(Position, getEntityIdFromKeys([ownArmy.entity_id])); if (!position) return; @@ -160,58 +182,33 @@ export const getBattleInfoByOwnArmyEntityId = (ownArmyEntityId: bigint): ExtraBa export const useBattlesByPosition = ({ x, y }: Position) => { const { setup: { - components: { Battle, Position, Army, Realm }, + components: { Battle, Position, Army, Structure, Protectee }, }, } = useDojo(); - const battleEntityIds = useEntityQuery([Has(Battle), HasValue(Position, { x, y })]); - return getExtraBattleInformation(battleEntityIds, Battle, Position, Realm).filter((battle) => - filterBattles(battle, Army), + return getExtraBattleInformation(battleEntityIds, Battle, Position, Structure).filter((battle) => + module.filterBattles(battle, Army, Protectee), )[0]; }; -export const getBattleByPosition = ({ x, y }: Position) => { +export const getBattleByPosition = () => { const { setup: { - components: { Battle, Position, Army, Realm }, + components: { Battle, Position, Army, Structure, Protectee }, }, } = useDojo(); - const battleEntityIds = runQuery([Has(Battle), HasValue(Position, { x, y })]); - return getExtraBattleInformation(Array.from(battleEntityIds), Battle, Position, Realm).filter((battle) => - filterBattles(battle, Army), - )[0]; -}; - -const filterBattles = (battle: BattleInfo, Army: any) => { - return !battleIsFinished(Army, battle) || (battle.isRealmBattle && !battleIsEmpty(Army, battle)); -}; - -export const battleIsFinished = (Army: Component, battle: BattleInfo) => { - const attackersEntityIds = runQuery([HasValue(Army, { battle_id: battle.entity_id, battle_side: "Attack" })]); - const defendersEntityIds = runQuery([HasValue(Army, { battle_id: battle.entity_id, battle_side: "Defence" })]); - - return ( - (Array.from(attackersEntityIds).length === 0 && - BigInt(battle.defence_army_health.current) / EternumGlobalConfig.troop.healthPrecision <= 0) || - (Array.from(defendersEntityIds).length === 0 && - BigInt(battle.attack_army_health.current) / EternumGlobalConfig.troop.healthPrecision <= 0) - ); -}; - -export const battleIsEmpty = (Army: Component, battle: BattleInfo) => { - const attackersEntityIds = runQuery([HasValue(Army, { battle_id: battle.entity_id, battle_side: "Attack" })]); - const defendersEntityIds = runQuery([HasValue(Army, { battle_id: battle.entity_id, battle_side: "Defence" })]); - - return Array.from(attackersEntityIds).length === 0 && Array.from(defendersEntityIds).length === 0; + const getBattle = ({ x, y }: Position) => { + const battleEntityIds = runQuery([Has(Battle), HasValue(Position, { x, y })]); + return getExtraBattleInformation(Array.from(battleEntityIds), Battle, Position, Structure).filter((battle) => + module.filterBattles(battle, Army, Protectee), + )[0]; + }; + return getBattle; }; -export const armyIsLosingSide = ( - army: ClientComponents["Army"]["schema"], - battle: ClientComponents["Battle"]["schema"], -) => { +export const filterBattles = (battle: BattleInfo, Army: Component, Protectee: Component) => { return ( - (String(army.battle_side) === "Attack" && BigInt(battle.attack_army_health.current) === 0n) || - (String(army.battle_side) === "Defence" && BigInt(battle.defence_army_health.current) === 0n) + !battleIsFinished(Army, battle) || (battle.isStructureBattle && protectorStillInBattle(Army, Protectee, battle)) ); }; diff --git a/client/src/hooks/helpers/battles/useBattlesUtils.tsx b/client/src/hooks/helpers/battles/useBattlesUtils.tsx new file mode 100644 index 000000000..f375b6a9a --- /dev/null +++ b/client/src/hooks/helpers/battles/useBattlesUtils.tsx @@ -0,0 +1,43 @@ +import { BattleSide } from "@bibliothecadao/eternum"; +import { Component, ComponentValue, Components, Has, HasValue, runQuery } from "@dojoengine/recs"; +import { BattleInfo } from "./useBattles"; + +export const battleIsFinished = (Army: Component, battle: BattleInfo) => { + const attackersEntityIds = getArmiesInBattleBySide(Army, battle, BattleSide.Attack); + const defendersEntityIds = getArmiesInBattleBySide(Army, battle, BattleSide.Defence); + + return ( + (Array.from(attackersEntityIds).length === 0 && battle.defence_army_health.current <= 0n) || + (Array.from(defendersEntityIds).length === 0 && battle.attack_army_health.current <= 0) + ); +}; + +export const battleIsEmpty = (Army: Component, battle: BattleInfo) => { + const attackersEntityIds = getArmiesInBattleBySide(Army, battle, BattleSide.Attack); + const defendersEntityIds = getArmiesInBattleBySide(Army, battle, BattleSide.Defence); + + return Array.from(attackersEntityIds).length === 0 && Array.from(defendersEntityIds).length === 0; +}; + +export const armyHasLost = (army: ComponentValue, battle: BattleInfo) => { + if (army.battle_id !== battle.entity_id) { + throw new Error("Army not in the correct battle"); + } + return ( + (army.battle_side === BattleSide.Attack && BigInt(battle.attack_army_health.current) <= 0) || + (army.battle_side === BattleSide.Defence && BigInt(battle.defence_army_health.current) <= 0) + ); +}; + +export const protectorStillInBattle = (Army: Component, Protectee: Component, battle: BattleInfo) => { + const protectorEntityIds = getProtectorsInBattle(Army, Protectee, battle); + return Array.from(protectorEntityIds).length > 0; +}; + +export const getProtectorsInBattle = (Army: Component, Protectee: Component, battle: BattleInfo) => { + return runQuery([Has(Protectee), HasValue(Army, { battle_id: battle.entity_id, battle_side: "Defence" })]); +}; + +export const getArmiesInBattleBySide = (Army: Component, battle: BattleInfo, battle_side: BattleSide) => { + return runQuery([Has(Army), HasValue(Army, { battle_id: battle.entity_id, battle_side: BattleSide[battle_side] })]); +}; diff --git a/client/src/hooks/helpers/useArmies.tsx b/client/src/hooks/helpers/useArmies.tsx index 9653dc916..4c1732d82 100644 --- a/client/src/hooks/helpers/useArmies.tsx +++ b/client/src/hooks/helpers/useArmies.tsx @@ -2,115 +2,138 @@ import { ClientComponents } from "@/dojo/createClientComponents"; import { getUIPositionFromColRow } from "@/ui/utils/utils"; import { EternumGlobalConfig, Position, UIPosition } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; -import { Component, Entity, Has, HasValue, Not, NotValue, getComponentValue, runQuery } from "@dojoengine/recs"; +import { + Component, + ComponentValue, + Entity, + Has, + HasValue, + Not, + NotValue, + getComponentValue, + runQuery, +} from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { useMemo } from "react"; import { shortString } from "starknet"; import { useDojo } from "../context/DojoContext"; -import { armyIsLosingSide, battleIsFinished, getExtraBattleInformation } from "./useBattles"; +import { getBattle } from "./battles/useBattles"; +import { armyHasLost, battleIsFinished } from "./battles/useBattlesUtils"; -export type ArmyInfo = ClientComponents["Army"]["schema"] & { +export type ArmyInfo = ComponentValue & { name: string; isMine: boolean; isMercenary: boolean; uiPos: UIPosition; offset: Position; -} & ClientComponents["Health"]["schema"] & - ClientComponents["Protectee"]["schema"] & - ClientComponents["Quantity"]["schema"] & - ClientComponents["Movable"]["schema"] & - ClientComponents["Capacity"]["schema"] & - ClientComponents["ArrivalTime"]["schema"] & - ClientComponents["Position"]["schema"] & - ClientComponents["EntityOwner"]["schema"] & - ClientComponents["Stamina"]["schema"] & - ClientComponents["Owner"]["schema"] & { realm: ClientComponents["Realm"]["schema"] } & { - homePosition: ClientComponents["Position"]["schema"]; - }; + health: ComponentValue; + position: ComponentValue; + owner: ComponentValue; + entityOwner: ComponentValue; + protectee: ComponentValue | undefined; + quantity: ComponentValue | undefined; + movable: ComponentValue | undefined; + capacity: ComponentValue | undefined; + arrivalTime: ComponentValue | undefined; + stamina: ComponentValue | undefined; + realm: ComponentValue | undefined; + homePosition: ComponentValue | undefined; +}; -const formatArmies = ( +export const formatArmies = ( armies: Entity[], playerAddress: string, - Army: Component, - Protectee: Component, - Name: Component, - Health: Component, - Quantity: Component, - Movable: Component, - Capacity: Component, - ArrivalTime: Component, - Position: Component, - EntityOwner: Component, - Owner: Component, - Realm: Component, - Stamina: Component, + Army: Component, + Protectee: Component, + Name: Component, + Health: Component, + Quantity: Component, + Movable: Component, + Capacity: Component, + ArrivalTime: Component, + Position: Component, + EntityOwner: Component, + Owner: Component, + Realm: Component, + Stamina: Component, ): ArmyInfo[] => { - return armies.map((id) => { - const army = getComponentValue(Army, id) as ClientComponents["Army"]["schema"]; - const protectee = getComponentValue(Protectee, id) as ClientComponents["Protectee"]["schema"]; - const health = getComponentValue(Health, id) as ClientComponents["Health"]["schema"]; - const quantity = getComponentValue(Quantity, id) as ClientComponents["Quantity"]["schema"]; - const movable = getComponentValue(Movable, id) as ClientComponents["Movable"]["schema"]; - const capacity = getComponentValue(Capacity, id) as ClientComponents["Capacity"]["schema"]; - const arrivalTime = getComponentValue(ArrivalTime, id) as ClientComponents["ArrivalTime"]["schema"]; - const position = getComponentValue(Position, id) as ClientComponents["Position"]["schema"]; - const entityOwner = getComponentValue(EntityOwner, id) as ClientComponents["EntityOwner"]["schema"]; - const stamina = getComponentValue(Stamina, id) as ClientComponents["Stamina"]["schema"]; - let owner = getComponentValue(Owner, id) as ClientComponents["Owner"]["schema"]; - if (!owner && entityOwner?.entity_owner_id) { - owner = getComponentValue( - Owner, - getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)]), - ) as ClientComponents["Owner"]["schema"]; - } - const name = getComponentValue(Name, id) as ClientComponents["EntityName"]["schema"]; - const realm = - entityOwner && - (getComponentValue( - Realm, - getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)]), - ) as ClientComponents["Realm"]["schema"]); - const homePosition = - realm && - (getComponentValue( - Position, - getEntityIdFromKeys([BigInt(realm.realm_id)]), - ) as ClientComponents["Position"]["schema"]); + return armies + .map((armyEntityId) => { + const army = getComponentValue(Army, armyEntityId); + if (!army) return undefined; + const health = getComponentValue(Health, armyEntityId); - const isMine = BigInt(owner?.address || 0) === BigInt(playerAddress); - const isMercenary = owner === undefined; - const ownGroupIndex = Number(army.entity_id) % 12; - const offset = calculateOffset(ownGroupIndex, 12); - const offsetToAvoidOverlapping = Math.random() * 1 - 0.5; - offset.y += offsetToAvoidOverlapping; + const position = getComponentValue(Position, armyEntityId); + if (!position) return undefined; - return { - ...army, - ...protectee, - ...health, - ...quantity, - ...movable, - ...capacity, - ...arrivalTime, - ...position, - ...entityOwner, - ...stamina, - ...owner, - realm, - homePosition, - isMine, - isMercenary, - offset, - uiPos: { ...getUIPositionFromColRow(position?.x || 0, position?.y || 0), z: 0.32 }, - - name: name - ? shortString.decodeShortString(name.name.toString()) - : `${protectee ? "🛡️" : "🗡️"}` + `Army ${army?.entity_id}`, - }; - }); + const entityOwner = getComponentValue(EntityOwner, armyEntityId); + if (!entityOwner) return undefined; + + let owner = getComponentValue(Owner, armyEntityId); + if (!owner && entityOwner?.entity_owner_id) { + owner = getComponentValue(Owner, getEntityIdFromKeys([BigInt(entityOwner.entity_owner_id)])); + } + if (!owner) return undefined; + + let healthClone = structuredClone(health); + if (healthClone) { + healthClone.current = + BigInt(healthClone.current) / + (BigInt(EternumGlobalConfig.resources.resourcePrecision) * EternumGlobalConfig.troop.healthPrecision); + healthClone.lifetime = + BigInt(healthClone.lifetime) / + (BigInt(EternumGlobalConfig.resources.resourcePrecision) * EternumGlobalConfig.troop.healthPrecision); + } else { + healthClone = { + entity_id: army.entity_id, + current: 0n, + lifetime: 0n, + }; + } + const protectee = getComponentValue(Protectee, armyEntityId); + const quantity = getComponentValue(Quantity, armyEntityId); + const movable = getComponentValue(Movable, armyEntityId); + const capacity = getComponentValue(Capacity, armyEntityId); + 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.realm_id)])); + + const isMine = BigInt(owner?.address || 0) === BigInt(playerAddress); + const isMercenary = owner === undefined; + const ownGroupIndex = Number(army.entity_id) % 12; + const offset = calculateOffset(ownGroupIndex, 12); + const offsetToAvoidOverlapping = Math.random() * 1 - 0.5; + offset.y += offsetToAvoidOverlapping; + + return { + ...army, + protectee, + health: healthClone, + quantity, + movable, + capacity, + arrivalTime, + position, + entityOwner, + stamina, + owner, + realm, + homePosition, + isMine, + isMercenary, + offset, + uiPos: { ...getUIPositionFromColRow(Number(position?.x || 0), Number(position?.y || 0)), z: 0.32 }, + name: name + ? shortString.decodeShortString(name.name.toString()) + : `${protectee ? "🛡️" : "🗡️"}` + `Army ${army.entity_id}`, + }; + }) + .filter((army): army is ArmyInfo => army !== undefined); }; -export const useArmies = () => { +export const useMovableArmies = () => { const { setup: { components: { @@ -139,7 +162,7 @@ export const useArmies = () => { Has(Health), Not(Protectee), NotValue(Movable, { sec_per_km: 0 }), - NotValue(Health, { current: 0n }), + HasValue(Army, { battle_id: 0n }), ]); return { @@ -160,7 +183,7 @@ export const useArmies = () => { Owner, Realm, Stamina, - ).filter((army) => isArmyAlive(army, Battle, Army, Position, Realm)), + ).filter((army) => isArmyAlive(army, Battle, Army)), }; }; @@ -206,7 +229,7 @@ export const useArmiesByEntityOwner = ({ entity_owner_entity_id }: { entity_owne Owner, Realm, Stamina, - ).filter((army) => isArmyAlive(army, Battle, Army, Position, Realm)); + ).filter((army) => isArmyAlive(army, Battle, Army)); }, [armies]); return { @@ -254,7 +277,7 @@ export const useArmiesByBattleId = (battle_id: bigint) => { Owner, Realm, Stamina, - ).filter((army) => isArmyAlive(army, Battle, Army, Position, Realm)); + ).filter((army) => isArmyAlive(army, Battle, Army)); }; export const getArmiesByBattleId = (battle_id: bigint) => { @@ -297,7 +320,7 @@ export const getArmiesByBattleId = (battle_id: bigint) => { Owner, Realm, Stamina, - ).filter((army) => isArmyAlive(army, Battle, Army, Position, Realm)); + ).filter((army) => isArmyAlive(army, Battle, Army)); }; export const useArmyByArmyEntityId = (entityId: bigint) => { @@ -340,7 +363,7 @@ export const useArmyByArmyEntityId = (entityId: bigint) => { Owner, Realm, Stamina, - ).filter((army) => isArmyAlive(army, Battle, Army, Position, Realm))[0]; + ).filter((army) => isArmyAlive(army, Battle, Army))[0]; }; export const usePositionArmies = ({ position }: { position: Position }) => { @@ -367,7 +390,7 @@ export const usePositionArmies = ({ position }: { position: Position }) => { account: { account }, } = useDojo(); - const allArmiesAtPosition = useEntityQuery([Has(Army), HasValue(Position, position)]); + const allArmiesAtPosition = useEntityQuery([Has(Army), HasValue(Position, { x: position.x, y: position.y })]); const allArmies = useMemo(() => { return formatArmies( @@ -386,7 +409,7 @@ export const usePositionArmies = ({ position }: { position: Position }) => { Owner, Realm, Stamina, - ).filter((army) => isArmyAlive(army, Battle, Army, Position, Realm)); + ).filter((army) => isArmyAlive(army, Battle, Army)); }, [allArmiesAtPosition]); const userArmies = useMemo(() => { @@ -464,7 +487,7 @@ export const getArmyByEntityId = () => { Owner, Realm, Stamina, - ).filter((army) => isArmyAlive(army, Battle, Army, Position, Realm))[0]; + ).filter((army) => isArmyAlive(army, Battle, Army))[0]; }; const getArmy = (entity_id: bigint) => { @@ -515,12 +538,8 @@ export const getArmiesAtPosition = () => { account: { account }, } = useDojo(); - const getArmies = (position: Position) => { - const allArmiesAtPosition = runQuery([ - Has(Army), - HasValue(Position, { x: position.x, y: position.y }), - HasValue(Army, { battle_id: 0n }), - ]); + const getArmies = ({ x, y }: Position) => { + const allArmiesAtPosition = runQuery([Has(Army), HasValue(Position, { x, y }), HasValue(Army, { battle_id: 0n })]); const userArmies = Array.from(allArmiesAtPosition).filter((armyEntityId: any) => { const entityOwner = getComponentValue(EntityOwner, armyEntityId); @@ -551,7 +570,8 @@ export const getArmiesAtPosition = () => { Owner, Realm, Stamina, - ).filter((army) => isArmyAlive(army, Battle, Army, Position, Realm)), + ).filter((army) => isArmyAlive(army, Battle, Army)), + opponentArmiesAtPosition: formatArmies( opponentArmies, account.address, @@ -568,7 +588,7 @@ export const getArmiesAtPosition = () => { Owner, Realm, Stamina, - ).filter((army) => isArmyAlive(army, Battle, Army, Position, Realm)), + ).filter((army) => isArmyAlive(army, Battle, Army)), }; }; @@ -594,22 +614,19 @@ const calculateOffset = (index: number, total: number) => { }; }; -export const checkIfArmyLostAFinishedBattle = (Battle: any, Army: any, army: any, Position: any, Realm: any) => { - const battle = getExtraBattleInformation([getEntityIdFromKeys([BigInt(army.battle_id)])], Battle, Position, Realm)[0]; - if (battle && armyIsLosingSide(army, battle!) && battleIsFinished(Army, battle)) { +export const checkIfArmyLostAFinishedBattle = (Battle: Component, Army: Component, army: ArmyInfo) => { + const battle = getBattle(getEntityIdFromKeys([BigInt(army?.battle_id || 0n)]), Battle); + if (battle && armyHasLost(army, battle as any) && battleIsFinished(Army, battle as any)) { return true; } return false; }; export const checkIfArmyAliveOnchain = (army: ArmyInfo) => { - if (army.current === undefined) return true; - return BigInt(army.current) / EternumGlobalConfig.troop.healthPrecision > 0; + if (army.protectee !== undefined) return true; + return BigInt(army.health.current) > 0; }; -export const isArmyAlive = (army: ArmyInfo, Battle: any, Army: any, Position: any, Realm: any) => { - return ( - (checkIfArmyAliveOnchain(army) && checkIfArmyLostAFinishedBattle(Battle, Army, army, Position, Realm) === false) || - BigInt(army?.protectee_id || 0) !== 0n - ); +export const isArmyAlive = (army: ArmyInfo, Battle: Component, Army: Component) => { + return checkIfArmyLostAFinishedBattle(Battle, Army, army) === false || army.protectee !== undefined; }; diff --git a/client/src/hooks/helpers/useBanks.tsx b/client/src/hooks/helpers/useBanks.tsx index 8a80035b8..6c2b4b85c 100644 --- a/client/src/hooks/helpers/useBanks.tsx +++ b/client/src/hooks/helpers/useBanks.tsx @@ -1,7 +1,7 @@ +import { Position } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; +import { Has, HasValue, getComponentValue } from "@dojoengine/recs"; import { useDojo } from "../context/DojoContext"; -import { Has, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; -import { Position } from "@bibliothecadao/eternum"; export const useGetBanks = (onlyMine?: boolean) => { const { @@ -21,7 +21,7 @@ export const useGetBanks = (onlyMine?: boolean) => { return { entityId: position.entity_id, - position, + position: { x: position.x, y: position.y }, }; }) .filter(Boolean) as { entityId: bigint; position: Position }[]; diff --git a/client/src/hooks/helpers/useBuildings.tsx b/client/src/hooks/helpers/useBuildings.tsx index 8cde63f56..5adabb83c 100644 --- a/client/src/hooks/helpers/useBuildings.tsx +++ b/client/src/hooks/helpers/useBuildings.tsx @@ -1,9 +1,9 @@ import { BuildingType } from "@bibliothecadao/eternum"; -import { useDojo } from "../context/DojoContext"; -import { CairoOption, CairoOptionVariant } from "starknet"; -import { uuid } from "@latticexyz/utils"; -import { getEntityIdFromKeys } from "@dojoengine/utils"; import { getComponentValue } from "@dojoengine/recs"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import { uuid } from "@latticexyz/utils"; +import { CairoOption, CairoOptionVariant } from "starknet"; +import { useDojo } from "../context/DojoContext"; export function useBuildings() { const { @@ -54,7 +54,7 @@ export function useBuildings() { outer_row: BigInt(outerrow), inner_col: BigInt(col), inner_row: BigInt(row), - category: "None", + category: "", produced_resource_type: 0, bonus_percent: 0n, entity_id: 0n, diff --git a/client/src/hooks/helpers/useCaravans.tsx b/client/src/hooks/helpers/useCaravans.tsx index b78f70e3b..020059930 100644 --- a/client/src/hooks/helpers/useCaravans.tsx +++ b/client/src/hooks/helpers/useCaravans.tsx @@ -1,7 +1,6 @@ import { getComponentValue } from "@dojoengine/recs"; -import { useDojo } from "../context/DojoContext"; import { getEntityIdFromKeys } from "../../ui/utils/utils"; -import { Position } from "@bibliothecadao/eternum"; +import { useDojo } from "../context/DojoContext"; export function useCaravan() { const { diff --git a/client/src/hooks/helpers/useEntities.tsx b/client/src/hooks/helpers/useEntities.tsx index 957766c3c..1b207ca37 100644 --- a/client/src/hooks/helpers/useEntities.tsx +++ b/client/src/hooks/helpers/useEntities.tsx @@ -1,58 +1,77 @@ +import { ClientComponents } from "@/dojo/createClientComponents"; import { getRealmNameById } from "@/ui/utils/realms"; import { divideByPrecision, getEntityIdFromKeys, getPosition } from "@/ui/utils/utils"; import { EntityType } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; -import { Has, HasValue, NotValue, getComponentValue, getEntitiesWithValue } from "@dojoengine/recs"; +import { ComponentValue, Has, HasValue, NotValue, getComponentValue } from "@dojoengine/recs"; import { shortString } from "starknet"; import { useDojo } from "../context/DojoContext"; import { useResources } from "./useResources"; +export interface PlayerStructures { + position: ComponentValue; + name: string | undefined; + entity_id?: bigint | undefined; + category?: string | undefined; +} + export const useEntities = () => { const { account: { account }, setup: { - components: { - Realm, - Owner, - EntityName, - ArrivalTime, - EntityOwner, - Movable, - Capacity, - Position, - Army, - Structure, - AddressName, - }, + components: { Realm, Owner, Position, Structure }, }, } = useDojo(); - const { getResourcesFromBalance } = useResources(); + const { getEntityName } = useEntitiesUtils(); const playerRealms = useEntityQuery([Has(Realm), HasValue(Owner, { address: BigInt(account.address) })]); const otherRealms = useEntityQuery([Has(Realm), NotValue(Owner, { address: BigInt(account.address) })]); const playerStructures = useEntityQuery([Has(Structure), HasValue(Owner, { address: BigInt(account.address) })]); - const getPlayerAddressFromEntity = (entityId: bigint) => { - const entityOwner = getComponentValue(EntityOwner, getEntityIdFromKeys([entityId])); - return entityOwner?.entity_owner_id - ? getComponentValue(Owner, getEntityIdFromKeys([entityOwner.entity_owner_id]))?.address - : undefined; - }; - - const getAddressNameFromEntity = (entityId: bigint) => { - const address = getPlayerAddressFromEntity(entityId); - if (!address) return; + return { + playerRealms: () => { + return playerRealms.map((id) => { + const realm = getComponentValue(Realm, id); + return { ...realm, position: getPosition(realm!.realm_id), name: getRealmNameById(realm!.realm_id) }; + }); + }, + otherRealms: () => { + return otherRealms.map((id) => { + const realm = getComponentValue(Realm, id); + return { ...realm, position: getPosition(realm!.realm_id), name: getRealmNameById(realm!.realm_id) }; + }); + }, + playerStructures: (): PlayerStructures[] => { + return playerStructures + .map((id) => { + const structure = getComponentValue(Structure, id); + const realm = getComponentValue(Realm, id); + const position = getComponentValue(Position, id); - const addressName = getComponentValue(AddressName, getEntityIdFromKeys([BigInt(address)])); + const structureName = getEntityName(structure!.entity_id); - return addressName ? shortString.decodeShortString(addressName.name.toString()) : undefined; + const name = realm + ? getRealmNameById(realm.realm_id) + : structureName + ? `${structure?.category} ${structureName}` + : structure?.category; + return { ...structure, position: position!, name }; + }) + .sort((a, b) => (a.category || "").localeCompare(b.category || "")); + }, }; +}; - const getEntityName = (entityId: bigint) => { - const entityName = getComponentValue(EntityName, getEntityIdFromKeys([entityId])); - return entityName ? shortString.decodeShortString(entityName.name.toString()) : entityId.toString(); - }; +export const useEntitiesUtils = () => { + const { + account: { account }, + setup: { + components: { EntityName, ArrivalTime, EntityOwner, Movable, Capacity, Position, Army, AddressName, Owner }, + }, + } = useDojo(); + + const { getResourcesFromBalance } = useResources(); const getEntityInfo = (entityId: bigint) => { const arrivalTime = getComponentValue(ArrivalTime, getEntityIdFromKeys([entityId])); @@ -96,49 +115,26 @@ export const useEntities = () => { }; }; - const allOwnedEntities = () => { - const realms = [...getEntitiesWithValue(Owner, { address: BigInt(account.address) })].map((id) => { - const realm = getComponentValue(Realm, id); - return { ...realm, position: getPosition(realm!.realm_id), name: getRealmNameById(realm!.realm_id) }; - }); - - return realms; + const getEntityName = (entityId: bigint) => { + const entityName = getComponentValue(EntityName, getEntityIdFromKeys([entityId])); + return entityName ? shortString.decodeShortString(entityName.name.toString()) : entityId.toString(); }; - return { - playerRealms: () => { - return playerRealms.map((id) => { - const realm = getComponentValue(Realm, id); - return { ...realm, position: getPosition(realm!.realm_id), name: getRealmNameById(realm!.realm_id) }; - }); - }, - otherRealms: () => { - return otherRealms.map((id) => { - const realm = getComponentValue(Realm, id); - return { ...realm, position: getPosition(realm!.realm_id), name: getRealmNameById(realm!.realm_id) }; - }); - }, - playerStructures: () => { - return playerStructures - .map((id) => { - const structure = getComponentValue(Structure, id); - const realm = getComponentValue(Realm, id); - const position = getComponentValue(Position, id); + const getAddressNameFromEntity = (entityId: bigint) => { + const address = getPlayerAddressFromEntity(entityId); + if (!address) return; - const structureName = getEntityName(structure!.entity_id); + const addressName = getComponentValue(AddressName, getEntityIdFromKeys([BigInt(address)])); - const name = realm - ? getRealmNameById(realm.realm_id) - : structureName - ? `${structure?.category} ${structureName}` - : structure?.category; - return { ...structure, position: position!, name }; - }) - .sort((a, b) => (a.category || "").localeCompare(b.category || "")); - }, - getEntityName, - getEntityInfo, - allOwnedEntities, - getAddressNameFromEntity, + return addressName ? shortString.decodeShortString(addressName.name.toString()) : undefined; + }; + + const getPlayerAddressFromEntity = (entityId: bigint) => { + const entityOwner = getComponentValue(EntityOwner, getEntityIdFromKeys([entityId])); + return entityOwner?.entity_owner_id + ? getComponentValue(Owner, getEntityIdFromKeys([entityOwner.entity_owner_id]))?.address + : undefined; }; + + return { getEntityName, getEntityInfo, getAddressNameFromEntity, getPlayerAddressFromEntity }; }; diff --git a/client/src/hooks/helpers/useGuilds.tsx b/client/src/hooks/helpers/useGuilds.tsx index 584423cd5..789e3fb1e 100644 --- a/client/src/hooks/helpers/useGuilds.tsx +++ b/client/src/hooks/helpers/useGuilds.tsx @@ -1,11 +1,11 @@ +import { ClientComponents } from "@/dojo/createClientComponents"; import { useEntityQuery } from "@dojoengine/react"; +import { Component, Entity, Has, HasValue, NotValue, getComponentValue, runQuery } from "@dojoengine/recs"; import { useMemo } from "react"; import { useDojo } from "../context/DojoContext"; -import { Component, Entity, Has, HasValue, NotValue, getComponentValue, runQuery } from "@dojoengine/recs"; -import { ClientComponents } from "@/dojo/createClientComponents"; -import { useRealm } from "./useRealm"; -import { useEntities } from "./useEntities"; import useLeaderBoardStore, { GuildPointsLeaderboardInterface } from "../store/useLeaderBoardStore"; +import { useEntitiesUtils } from "./useEntities"; +import { useRealm } from "./useRealm"; export type GuildAndName = ClientComponents["Guild"]["schema"] & { name: string } & { rank: number | string }; export type GuildMemberAndName = ClientComponents["GuildMember"]["schema"] & { name: string } & { @@ -24,7 +24,7 @@ export const useGuilds = () => { account: { account }, } = useDojo(); - const { getEntityName } = useEntities(); + const { getEntityName } = useEntitiesUtils(); const { getAddressName } = useRealm(); const guildPointsLeaderboard = useLeaderBoardStore((state) => state.guildPointsLeaderboard); @@ -66,7 +66,7 @@ export const useGuilds = () => { const getAddressWhitelist = useMemo( () => (address: bigint) => { // TODO : CONSTANT 1n - const addressWhitelist = useEntityQuery([HasValue(GuildWhitelist, { address: address, is_whitelisted: 1n })]); + const addressWhitelist = useEntityQuery([HasValue(GuildWhitelist, { address, is_whitelisted: 1n })]); return { addressWhitelist: formatAddressWhitelist(addressWhitelist, GuildWhitelist, getEntityName), }; diff --git a/client/src/hooks/helpers/useHexPosition.tsx b/client/src/hooks/helpers/useHexPosition.tsx index 1810655d2..ba9215f5d 100644 --- a/client/src/hooks/helpers/useHexPosition.tsx +++ b/client/src/hooks/helpers/useHexPosition.tsx @@ -1,12 +1,12 @@ -import { useEffect, useMemo } from "react"; import useUIStore from "@/hooks/store/useUIStore"; import { getNeighborHexes } from "@bibliothecadao/eternum"; -import { useSearch } from "wouter/use-location"; import { useEntityQuery } from "@dojoengine/react"; import { Has, HasValue, getComponentValue } from "@dojoengine/recs"; +import { useEffect, useMemo } from "react"; +import { useSearch } from "wouter/use-location"; import { useDojo } from "../context/DojoContext"; -import useRealmStore from "../store/useRealmStore"; import useLeaderBoardStore from "../store/useLeaderBoardStore"; +import useRealmStore from "../store/useRealmStore"; export enum HexType { BANK = "bank", @@ -22,7 +22,6 @@ export const useHexPosition = () => { const finishedHyperstructures = useLeaderBoardStore((state) => state.finishedHyperstructures); const { - account, setup: { components: { Structure, Position, Owner }, }, @@ -47,6 +46,7 @@ export const useHexPosition = () => { const hexType: HexType = useMemo(() => { if (!structure) return HexType.EMPTY; + let category = structure.category.toUpperCase(); if (structure.category === HexType.HYPERSTRUCTURE) { category = finishedHyperstructures.some((evt) => { diff --git a/client/src/hooks/helpers/useHyperstructures.tsx b/client/src/hooks/helpers/useHyperstructures.tsx index bb6d91644..1b3737128 100644 --- a/client/src/hooks/helpers/useHyperstructures.tsx +++ b/client/src/hooks/helpers/useHyperstructures.tsx @@ -1,18 +1,19 @@ import { ClientComponents } from "@/dojo/createClientComponents"; import { EternumGlobalConfig, HYPERSTRUCTURE_TOTAL_COSTS_SCALED } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; -import { Component, ComponentValue, Entity, Has, HasValue, Type, getComponentValue, runQuery } from "@dojoengine/recs"; +import { Component, ComponentValue, Entity, Has, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; import { toInteger } from "lodash"; import { shortString } from "starknet"; import { useDojo } from "../context/DojoContext"; import { ResourceMultipliers, TOTAL_CONTRIBUTABLE_AMOUNT } from "../store/useLeaderBoardStore"; -export type Hyperstructure = ClientComponents["Structure"]["schema"] & - ClientComponents["Progress"]["schema"][] & - ClientComponents["Contribution"]["schema"][] & - ClientComponents["Position"]["schema"] & { - entityIdPoseidon: Entity; - } & { name: string }; +export type Hyperstructure = ComponentValue & { + entityIdPoseidon: Entity; + name: string; + progress: ComponentValue; + contribution: ComponentValue; + position: ComponentValue; +}; export type ProgressWithPercentage = { percentage: number; @@ -95,10 +96,7 @@ const getContributions = (hyperstructureEntityId: bigint, Contribution: Componen }; const getAllProgressesAndTotalPercentage = ( - progresses: ( - | ComponentValue<{ hyperstructure_entity_id: Type.BigInt; resource_type: Type.Number; amount: Type.Number }> - | undefined - )[], + progresses: (ComponentValue | undefined)[], hyperstructureEntityId: bigint, ) => { diff --git a/client/src/hooks/helpers/useLevel.tsx b/client/src/hooks/helpers/useLevel.tsx deleted file mode 100644 index 068766f2d..000000000 --- a/client/src/hooks/helpers/useLevel.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { getComponentValue } from "@dojoengine/recs"; -import { useDojo } from "../context/DojoContext"; -import useBlockchainStore from "../store/useBlockchainStore"; -import { getEntityIdFromKeys } from "../../ui/utils/utils"; - -export enum LevelIndex { - FOOD = 1, - RESOURCE = 2, - TRAVEL = 3, - COMBAT = 4, -} - -const LEVEL_DECAY = 0.1; -const LEVEL_BASE_MULTIPLIER = 25; - -export function useLevel() { - const { - setup: { - components: { Level }, - }, - } = useDojo(); - - const nextBlockTimestamp = useBlockchainStore((state) => state.nextBlockTimestamp); - - const getRealmBonuses = (level: number) => { - return [ - { - bonusType: LevelIndex.FOOD, - bonusAmount: getRealmLevelBonus(level, LevelIndex.FOOD) - 100, - }, - { - bonusType: LevelIndex.RESOURCE, - bonusAmount: getRealmLevelBonus(level, LevelIndex.RESOURCE) - 100, - }, - { - bonusType: LevelIndex.TRAVEL, - bonusAmount: getRealmLevelBonus(level, LevelIndex.TRAVEL) - 100, - }, - { - bonusType: LevelIndex.COMBAT, - bonusAmount: getRealmLevelBonus(level, LevelIndex.COMBAT) - 100, - }, - ]; - }; - - const getHyperstructureBonuses = (level: number) => { - return [ - { - bonusType: LevelIndex.FOOD, - bonusAmount: getHyperstructureLevelBonus(level, LevelIndex.FOOD) - 100, - }, - { - bonusType: LevelIndex.RESOURCE, - bonusAmount: getHyperstructureLevelBonus(level, LevelIndex.RESOURCE) - 100, - }, - { - bonusType: LevelIndex.TRAVEL, - bonusAmount: getHyperstructureLevelBonus(level, LevelIndex.TRAVEL) - 100, - }, - { - bonusType: LevelIndex.COMBAT, - bonusAmount: getHyperstructureLevelBonus(level, LevelIndex.COMBAT) - 100, - }, - ]; - }; - - const getEntityLevel = (entityId: bigint): { level: number; timeLeft: number } | undefined => { - if (!nextBlockTimestamp) return undefined; - const level = getComponentValue(Level, getEntityIdFromKeys([entityId])) || { - level: 0, - valid_until: nextBlockTimestamp, - }; - - let trueLevel = level.level; - // calculate true level - if (level.valid_until > nextBlockTimestamp) { - trueLevel = level.level; - } else { - const weeksPassed = Math.floor((nextBlockTimestamp - level.valid_until) / 604800) + 1; - trueLevel = Math.max(0, level.level - weeksPassed); - } - - let timeLeft: number; - if (trueLevel === 0) { - timeLeft = 0; - } else { - if (nextBlockTimestamp >= level.valid_until) { - timeLeft = 604800 - ((nextBlockTimestamp - level.valid_until) % 604800); - } else { - timeLeft = level.valid_until - nextBlockTimestamp; - } - } - - return { level: trueLevel, timeLeft }; - }; - - const getRealmLevelBonus = (level: number, levelIndex: LevelIndex) => { - if (level < 5) { - return 100; - } else { - let tier = (level % 4) + 1 > levelIndex ? Math.floor(level / 4) + 1 : Math.floor(level / 4); - return Math.round(((1 - (1 - LEVEL_DECAY) ** (tier - 1)) / LEVEL_DECAY) * LEVEL_BASE_MULTIPLIER) + 100; - } - }; - - const getHyperstructureLevelBonus = (level: number, levelIndex: LevelIndex) => { - return level >= levelIndex ? 100 + LEVEL_BASE_MULTIPLIER : 100; - }; - - return { - getEntityLevel, - getRealmLevelBonus, - getHyperstructureLevelBonus, - getHyperstructureBonuses, - getRealmBonuses, - }; -} diff --git a/client/src/hooks/helpers/useRealm.tsx b/client/src/hooks/helpers/useRealm.tsx index b8855d018..a0f687905 100644 --- a/client/src/hooks/helpers/useRealm.tsx +++ b/client/src/hooks/helpers/useRealm.tsx @@ -74,7 +74,7 @@ export function useRealm() { const position = getPosition(BigInt(nextRealmIdFromOrder)); // check if there is a structure on position, if no structure we can keep this realm Id - if (Array.from(runQuery([HasValue(Position, position), Has(Structure)])).length === 0) { + if (Array.from(runQuery([HasValue(Position, { x: position.x, y: position.y }), Has(Structure)])).length === 0) { return BigInt(nextRealmIdFromOrder); } else { latestRealmIdFromOrder = nextRealmIdFromOrder; diff --git a/client/src/hooks/helpers/useResources.tsx b/client/src/hooks/helpers/useResources.tsx index 82dff66f0..1963cdd2f 100644 --- a/client/src/hooks/helpers/useResources.tsx +++ b/client/src/hooks/helpers/useResources.tsx @@ -121,7 +121,7 @@ export function useResources() { const arrivalTime = getComponentValue(ArrivalTime, id); const position = getComponentValue(Position, id); const army = getArmy(position?.entity_id || BigInt(0)); - if (army && !isArmyAlive(army, Battle, Army, Position, Realm)) return undefined; + if (army && !isArmyAlive(army, Battle, Army)) return undefined; return { id, entityId: position?.entity_id || BigInt(""), @@ -279,7 +279,9 @@ export function useOwnedEntitiesOnPosition() { const getOwnedEntityOnPosition = (entityId: bigint) => { const position = getComponentValue(Position, getEntityIdFromKeys([entityId])); - const depositEntityIds = position ? getOwnedEntitiesOnPosition(BigInt(account.address), position) : []; + const depositEntityIds = position + ? getOwnedEntitiesOnPosition(BigInt(account.address), { x: Number(position.x), y: Number(position.y) }) + : []; return depositEntityIds[0]; }; diff --git a/client/src/hooks/helpers/useRoads.tsx b/client/src/hooks/helpers/useRoads.tsx index 002ba2f9b..50b9b6d36 100644 --- a/client/src/hooks/helpers/useRoads.tsx +++ b/client/src/hooks/helpers/useRoads.tsx @@ -1,11 +1,11 @@ +import { RoadInterface } from "@bibliothecadao/eternum"; import { useComponentValue, useEntityQuery } from "@dojoengine/react"; -import { useDojo } from "../context/DojoContext"; -import { getEntityIdFromKeys, getPosition } from "../../ui/utils/utils"; -import { useEffect, useMemo, useState } from "react"; import { Entity, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; -import useRealmStore from "../store/useRealmStore"; +import { useEffect, useMemo, useState } from "react"; import { getRealm, getRealmIdByPosition, getRealmNameById } from "../../ui/utils/realms"; -import { RoadInterface } from "@bibliothecadao/eternum"; +import { getEntityIdFromKeys, getPosition } from "../../ui/utils/utils"; +import { useDojo } from "../context/DojoContext"; +import useRealmStore from "../store/useRealmStore"; export function useRoads() { const { diff --git a/client/src/hooks/helpers/useStamina.tsx b/client/src/hooks/helpers/useStamina.tsx index 506514fa5..3b08dea95 100644 --- a/client/src/hooks/helpers/useStamina.tsx +++ b/client/src/hooks/helpers/useStamina.tsx @@ -1,4 +1,3 @@ -import { ClientComponents } from "@/dojo/createClientComponents"; import { ResourcesIds, WORLD_CONFIG_ID } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; import { Component, Has, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; @@ -29,7 +28,7 @@ export const useStamina = () => { amount: getMaxStamina(armyEntity.troops, StaminaConfig), }; } - return staminaEntity as unknown as ClientComponents["Stamina"]["schema"]; + return staminaEntity; }; const getStamina = ({ @@ -52,7 +51,7 @@ export const useStamina = () => { amount: getMaxStamina(armyEntity!.troops, StaminaConfig), }; } - return staminaEntity as unknown as ClientComponents["Stamina"]["schema"]; + return staminaEntity; }; const getMaxStaminaByEntityId = (travelingEntityId: bigint): number => { @@ -69,13 +68,12 @@ export const useStamina = () => { const stamina = getStamina({ travelingEntityId: entityId, currentArmiesTick }); - // substract the costs Stamina.addOverride(overrideId, { entity, value: { entity_id: entityId, - last_refill_tick: stamina.last_refill_tick, - amount: stamina.amount - cost, + last_refill_tick: stamina?.last_refill_tick || 0, + amount: stamina?.amount ? stamina.amount - cost : 0, }, }); }; diff --git a/client/src/hooks/helpers/useStructures.tsx b/client/src/hooks/helpers/useStructures.tsx index 8b2d4638b..5d9ce2054 100644 --- a/client/src/hooks/helpers/useStructures.tsx +++ b/client/src/hooks/helpers/useStructures.tsx @@ -2,34 +2,34 @@ import { ClientComponents } from "@/dojo/createClientComponents"; import { unpackResources } from "@/ui/utils/packedData"; import { getRealm, getRealmNameById } from "@/ui/utils/realms"; import { calculateDistance } from "@/ui/utils/utils"; -import { EternumGlobalConfig, Position } from "@bibliothecadao/eternum"; +import { EternumGlobalConfig, Position, StructureType } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; -import { Component, Has, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; +import { ComponentValue, Has, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import { useMemo } from "react"; import { shortString } from "starknet"; import { useDojo } from "../context/DojoContext"; import { ArmyInfo, getArmyByEntityId } from "./useArmies"; -export type Realm = ClientComponents["Realm"]["schema"] & { +export type Realm = ComponentValue & { resources: number[]; self: boolean; name: string; protector: ArmyInfo | undefined; }; -export type Structure = ClientComponents["Structure"]["schema"] & { +export type Structure = ComponentValue & { isMine: boolean; isMercenary: boolean; name: string; protector: ArmyInfo | undefined; - owner: ClientComponents["Owner"]["schema"]; - entityOwner: ClientComponents["EntityOwner"]["schema"]; + owner: ComponentValue; + entityOwner: ComponentValue; }; -export type FullStructure = ClientComponents["Structure"]["schema"] & { - entityOwner: ClientComponents["EntityOwner"]["schema"]; - owner: ClientComponents["Owner"]["schema"]; +export type FullStructure = ComponentValue & { + entityOwner: ComponentValue; + owner: ComponentValue; protector: ArmyInfo | undefined; isMine: boolean; }; @@ -45,19 +45,22 @@ export const useStructuresPosition = ({ position }: { position: Position }) => { const { getAliveArmy } = getArmyByEntityId(); const useFormattedRealmAtPosition = () => { - const realmsAtPosition = useEntityQuery([HasValue(Position, position), HasValue(Structure, { category: "Realm" })]); - const formattedRealmAtPosition: Realm = realmsAtPosition.map((realm_entity_id: any) => { - const realm = getComponentValue(Realm as Component, realm_entity_id) as ClientComponents["Realm"]["schema"]; + const realmsAtPosition = useEntityQuery([ + HasValue(Position, position), + HasValue(Structure, { category: StructureType[StructureType.Realm] }), + ]); + const formattedRealmAtPosition: Realm | undefined = realmsAtPosition.map((realm_entity_id: any) => { + const realm = getComponentValue(Realm, realm_entity_id); + if (!realm) return; const entityOwner = getComponentValue(EntityOwner, realm_entity_id); + if (!entityOwner) return; const owner = getComponentValue(Owner, getEntityIdFromKeys([entityOwner?.entity_owner_id || 0n])); + if (!owner) return; const resources = unpackResources(BigInt(realm?.resource_types_packed || 0n), realm?.resource_types_count || 0); const name = getRealmNameById(BigInt(realm?.realm_id) || 0n); - let protector: ClientComponents["Protector"]["schema"] | undefined | ArmyInfo = getComponentValue( - Protector, - realm_entity_id, - ) as unknown as ClientComponents["Protector"]["schema"]; - protector = protector ? getAliveArmy(BigInt(protector.army_id)) : undefined; + const protectorArmy = getComponentValue(Protector, realm_entity_id); + const protector = protectorArmy ? getAliveArmy(BigInt(protectorArmy.army_id)) : undefined; const fullRealm = { ...realm, @@ -77,24 +80,17 @@ export const useStructuresPosition = ({ position }: { position: Position }) => { const structuresAtPosition = useEntityQuery([HasValue(Position, position), Has(Structure)]); const formattedStructureAtPosition: Structure | undefined = structuresAtPosition.map((entityId: any) => { - const structure = getComponentValue(Structure, entityId) as unknown as ClientComponents["Structure"]["schema"]; + const structure = getComponentValue(Structure, entityId); if (!structure) { return; } - const entityOwner = getComponentValue( - EntityOwner, - entityId, - ) as unknown as ClientComponents["EntityOwner"]["schema"]; - const owner = getComponentValue( - Owner, - getEntityIdFromKeys([BigInt(entityOwner?.entity_owner_id) || 0n]), - ) as unknown as ClientComponents["Owner"]["schema"]; - let protector: ClientComponents["Protector"]["schema"] | undefined | ArmyInfo = getComponentValue( - Protector, - entityId, - ) as unknown as ClientComponents["Protector"]["schema"]; - protector = protector ? getAliveArmy(BigInt(protector.army_id)) : undefined; + const entityOwner = getComponentValue(EntityOwner, entityId); + if (!entityOwner) return; + const owner = getComponentValue(Owner, getEntityIdFromKeys([entityOwner?.entity_owner_id || 0n])); + if (!owner) return; + const protectorArmy = getComponentValue(Protector, entityId); + const protector = protectorArmy ? getAliveArmy(BigInt(protectorArmy.army_id)) : undefined; const onChainName = getComponentValue(EntityName, entityId); @@ -129,7 +125,7 @@ export const useStructuresPosition = ({ position }: { position: Position }) => { }; }; -export const getStructureAtPosition = (position: Position) => { +export const getStructureAtPosition = ({ x, y }: Position): Structure | undefined => { const { account: { account }, setup: { @@ -140,36 +136,24 @@ export const getStructureAtPosition = (position: Position) => { const { getAliveArmy } = getArmyByEntityId(); const structure = useMemo(() => { - const structureAtPosition = runQuery([HasValue(Position, position), Has(Structure)]); + const structureAtPosition = runQuery([HasValue(Position, { x, y }), Has(Structure)]); const structureEntityId = Array.from(structureAtPosition)[0]; - const structure = getComponentValue( - Structure, - structureEntityId, - ) as unknown as ClientComponents["Structure"]["schema"]; - if (!structure) { - return; - } - - const entityOwner = getComponentValue( - EntityOwner, - structureEntityId, - ) as unknown as ClientComponents["EntityOwner"]["schema"]; - const owner = getComponentValue( - Owner, - getEntityIdFromKeys([BigInt(entityOwner?.entity_owner_id) || 0n]), - ) as unknown as ClientComponents["Owner"]["schema"]; - - let protector: ClientComponents["Protector"]["schema"] | undefined | ArmyInfo = getComponentValue( - Protector, - structureEntityId, - ) as unknown as ClientComponents["Protector"]["schema"]; - protector = protector ? getAliveArmy(BigInt(protector.army_id)) : undefined; + const structure = getComponentValue(Structure, structureEntityId); + if (!structure) return; + + const entityOwner = getComponentValue(EntityOwner, structureEntityId); + if (!entityOwner) return; + + const owner = getComponentValue(Owner, getEntityIdFromKeys([entityOwner?.entity_owner_id || 0n])); + if (!owner) return undefined; + const protectorArmy = getComponentValue(Protector, structureEntityId); + const protector = protectorArmy ? getAliveArmy(BigInt(protectorArmy.army_id)) : undefined; const onChainName = getComponentValue(EntityName, structureEntityId); const name = - String(structure.category) === "Realm" + structure.category === StructureType[StructureType.Realm] ? getRealmNameById(getComponentValue(Realm, structureEntityId)!.realm_id) : onChainName ? shortString.decodeShortString(onChainName.name.toString()) @@ -180,11 +164,11 @@ export const getStructureAtPosition = (position: Position) => { entityOwner, owner, name, - protector: protector as ArmyInfo | undefined, + protector, isMine: BigInt(owner?.address || 0) === BigInt(account.address), isMercenary: owner === undefined, }; - }, [position]); + }, [x, y]); return structure; }; diff --git a/client/src/hooks/helpers/useTrade.tsx b/client/src/hooks/helpers/useTrade.tsx index 22df805ab..51fafc93d 100644 --- a/client/src/hooks/helpers/useTrade.tsx +++ b/client/src/hooks/helpers/useTrade.tsx @@ -1,17 +1,17 @@ -import { HasValue, getComponentValue, runQuery, Entity, NotValue } from "@dojoengine/recs"; -import { useDojo } from "../context/DojoContext"; +import { getRealmNameById } from "@/ui/utils/realms"; import { MarketInterface, Resource, ResourcesIds } from "@bibliothecadao/eternum"; +import { useEntityQuery } from "@dojoengine/react"; +import { Entity, HasValue, getComponentValue } from "@dojoengine/recs"; import { useEffect, useMemo, useState } from "react"; -import useRealmStore from "../store/useRealmStore"; -import { getEntityIdFromKeys } from "../../ui/utils/utils"; +import { shortString } from "starknet"; import { calculateRatio } from "../../ui/components/cityview/realm/trade/Market/MarketOffer"; import { SortInterface } from "../../ui/elements/SortButton"; +import { getEntityIdFromKeys } from "../../ui/utils/utils"; +import { useDojo } from "../context/DojoContext"; import useBlockchainStore from "../store/useBlockchainStore"; -import useMarketStore, { isLordsMarket } from "../store/useMarketStore"; -import { useEntityQuery } from "@dojoengine/react"; +import useMarketStore from "../store/useMarketStore"; +import useRealmStore from "../store/useRealmStore"; import { useEntities } from "./useEntities"; -import { shortString } from "starknet"; -import { getRealmNameById } from "@/ui/utils/realms"; type TradeResourcesFromViewpoint = { resourcesGet: Resource[]; @@ -91,7 +91,7 @@ export function useTrade() { makerId: trade.maker_id, takerId: trade.taker_id, makerOrder: makerRealm?.order, - expiresAt: trade.expires_at, + expiresAt: Number(trade.expires_at), takerGets, makerGets, ratio: calculateRatio(makerGets, takerGets), diff --git a/client/src/hooks/helpers/useTravel.tsx b/client/src/hooks/helpers/useTravel.tsx index 320f533ed..0efcc8a03 100644 --- a/client/src/hooks/helpers/useTravel.tsx +++ b/client/src/hooks/helpers/useTravel.tsx @@ -1,9 +1,9 @@ +import { calculateDistance } from "@/ui/utils/utils"; import { EternumGlobalConfig, Position } from "@bibliothecadao/eternum"; -import { useDojo } from "../context/DojoContext"; import { getComponentValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; -import { calculateDistance } from "@/ui/utils/utils"; import { uuid } from "@latticexyz/utils"; +import { useDojo } from "../context/DojoContext"; import { useStamina } from "./useStamina"; interface TravelToHexProps { travelingEntityId: bigint | undefined; @@ -26,7 +26,11 @@ export function useTravel() { const fromPosition = getComponentValue(components.Position, getEntityIdFromKeys([fromId])); const toPosition = getComponentValue(components.Position, getEntityIdFromKeys([toId])); if (!fromPosition || !toPosition) return; - const distanceFromPosition = calculateDistance(fromPosition, toPosition) ?? 0; + const distanceFromPosition = + calculateDistance( + { x: Number(fromPosition.x), y: Number(fromPosition.y) }, + { x: Number(toPosition.x), y: Number(toPosition.y) }, + ) ?? 0; const onewayTime = Math.floor(((distanceFromPosition / speed) * 3600) / 60); return pickup ? onewayTime * 2 : onewayTime; }; diff --git a/client/src/hooks/store/_mapStore.tsx b/client/src/hooks/store/_mapStore.tsx index aa7062358..f60608adb 100644 --- a/client/src/hooks/store/_mapStore.tsx +++ b/client/src/hooks/store/_mapStore.tsx @@ -149,8 +149,8 @@ export const useSetExistingStructures = () => { if (!position || !structure || !owner) return null; const isMine = owner?.address === BigInt(account.address); return { - col: position.x, - row: position.y, + col: Number(position.x), + row: Number(position.y), type: type as StructureType, entity: entity, entityId: structure.entity_id, diff --git a/client/src/hooks/store/useBlockchainStore.tsx b/client/src/hooks/store/useBlockchainStore.tsx index e3cdfd2db..03d99ba50 100644 --- a/client/src/hooks/store/useBlockchainStore.tsx +++ b/client/src/hooks/store/useBlockchainStore.tsx @@ -53,8 +53,8 @@ export const useFetchBlockchainData = () => { if (timestamp && timestamp !== currentTimestamp) { // Check if fetched timestamp is different from current state setNextBlockTimestamp(timestamp); - setCurrentDefaultTick(Math.floor(timestamp / tickConfigDefault!.tick_interval_in_seconds)); - setCurrentArmiesTick(Math.floor(timestamp / tickConfigArmies!.tick_interval_in_seconds)); + setCurrentDefaultTick(Math.floor(timestamp / Number(tickConfigDefault!.tick_interval_in_seconds))); + setCurrentArmiesTick(Math.floor(timestamp / Number(tickConfigArmies!.tick_interval_in_seconds))); } }; diff --git a/client/src/hooks/store/useQuestStore.tsx b/client/src/hooks/store/useQuestStore.tsx index fe85dbc6a..6288ab597 100644 --- a/client/src/hooks/store/useQuestStore.tsx +++ b/client/src/hooks/store/useQuestStore.tsx @@ -343,7 +343,7 @@ const armyHasTroops = (entityArmies: ArmyInfo[]) => { const armyHasTraveled = (entityArmies: ArmyInfo[], realmPosition: { x: number; y: number }) => { if (entityArmies && entityArmies[0] && realmPosition) { - return entityArmies[0].x != realmPosition.x || entityArmies[0].y != realmPosition.y; + return entityArmies[0].position.x != realmPosition.x || entityArmies[0].position.y != realmPosition.y; } return false; }; diff --git a/client/src/hooks/useUISound.tsx b/client/src/hooks/useUISound.tsx index 2218faa5a..42f36fccc 100644 --- a/client/src/hooks/useUISound.tsx +++ b/client/src/hooks/useUISound.tsx @@ -315,11 +315,9 @@ export const useBuildingSound = () => { const { play: playBuildFishingVillage } = useUiSounds(soundSelector.buildFishingVillage); const { play: playBuildMine } = useUiSounds(soundSelector.buildMine); const { play: playBuildStables } = useUiSounds(soundSelector.buildStables); - const { play: playBuildWorkHut } = useUiSounds(soundSelector.buildWorkHut); const { play: playBuildArcherRange } = useUiSounds(soundSelector.buildArcherRange); const { play: playBuildBarracks } = useUiSounds(soundSelector.buildBarracks); const { play: playBuildMarket } = useUiSounds(soundSelector.buildMarket); - const { play: playBuildStorehouse } = useUiSounds(soundSelector.buildStorehouse); const { play: playLumberMill } = useUiSounds(soundSelector.buildLumberMill); const playBuildingSound = (buildingType: BuildingType | ResourceMiningTypes) => { diff --git a/client/src/setupTests.ts b/client/src/setupTests.ts new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/ui/components/cityview/realm/RealmInfoComponent.tsx b/client/src/ui/components/cityview/realm/RealmInfoComponent.tsx deleted file mode 100644 index 35ea33c6a..000000000 --- a/client/src/ui/components/cityview/realm/RealmInfoComponent.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import { OrderIcon } from "../../../elements/OrderIcon"; -import useRealmStore from "../../../../hooks/store/useRealmStore"; -import realmsNames from "../../../../data/geodata/realms.json"; -import { Resource, getLevelingCost, orderNameDict } from "@bibliothecadao/eternum"; -import clsx from "clsx"; -import { useGetRealm } from "../../../../hooks/helpers/useRealm"; -import { useDojo } from "../../../../hooks/context/DojoContext"; -import { LevelingBonusIcons } from "./leveling/Leveling"; -import { LevelingPopup } from "./leveling/LevelingPopup"; -import { useEffect, useMemo, useState } from "react"; -import useUIStore from "../../../../hooks/store/useUIStore"; -import { useLevel } from "../../../../hooks/helpers/useLevel"; -import { useLocation } from "wouter"; -import { RealmLevel } from "../../../elements/RealmLevel"; -import Button from "../../../elements/Button"; -import { getEntityIdFromKeys } from "@dojoengine/utils"; -import { getComponentValue } from "@dojoengine/recs"; - -type RealmInfoComponentProps = {}; - -const bgColorsByOrder = { - power: "#6D4C11", - anger: "#4F0916", - brilliance: "#287F4A", - detection: "#0B3D1F", - enlightenment: "#063658", - fox: "#612C0F", - fury: "#490626", - giants: "#6D4C11", - perfection: "#310B4F", - reflection: "#124A4A", - skill: "#11105F", - titans: "#5C1437", - twins: "#060E39", - vitriol: "#273F0F", - rage: "#61290A", - protection: "#0E543F", -}; - -export const RealmInfoComponent = ({}: RealmInfoComponentProps) => { - const { - account: { accountDisplay }, - setup: { - components: { Resource }, - }, - } = useDojo(); - const [_location, setLocation] = useLocation(); - const [levelUpProgress, setLevelUpProgress] = useState(0); - - const [showRealmLevelUp, setShowRealmLevelUp] = useState(false); - const setTooltip = useUIStore((state) => state.setTooltip); - const moveCameraToRealm = useUIStore((state) => state.moveCameraToRealm); - const setIsLoadingScreenEnabled = useUIStore((state) => state.setIsLoadingScreenEnabled); - - const { realmEntityId } = useRealmStore(); - const { realm } = useGetRealm(realmEntityId); - - const { getRealmBonuses, getEntityLevel } = useLevel(); - - const realmLevel = realmEntityId ? getEntityLevel(realmEntityId) : undefined; - - const realmBonuses = useMemo(() => { - const bonus = realmLevel ? getRealmBonuses(realmLevel.level) : []; - return bonus.reduce((acc, curr) => acc + curr.bonusAmount, 0) === 0 ? undefined : bonus; - }, [realmLevel]); - - const showOnMap = () => { - setLocation("/map"); - setIsLoadingScreenEnabled(true); - moveCameraToRealm(Number(realm?.realmId), 0.01); - }; - - // TODO: get info from contract config file - // calculate the costs of building/buying tools - let costResources = useMemo(() => { - if (realmLevel) { - return getLevelingCost(realmLevel.level + 1); - } else return []; - }, [realmLevel]); - - useEffect(() => { - let missingResources: Resource[] = []; - costResources.forEach(({ resourceId, amount }) => { - const realmResource = getComponentValue( - Resource, - getEntityIdFromKeys([BigInt(realmEntityId), BigInt(resourceId)]), - ); - - if (!realmResource || realmResource.balance < amount) { - missingResources.push({ resourceId, amount: amount - (Number(realmResource?.balance) || 0) }); - } - }); - setLevelUpProgress(Math.round(((costResources.length - missingResources.length) / costResources.length) * 100)); - }, [costResources]); - - const canLevelUp = useMemo(() => { - return levelUpProgress === 100; - }, [levelUpProgress]); - - return ( - <> - {realm && ( -
-
-
-
{accountDisplay}
-
-
-
- -
- {realmsNames.features[Number(realm.realmId) - 1].name} - { - if (!realmBonuses) return; - setTooltip({ - position: "top", - content: ( - <>{} - ), - }); - }} - onMouseLeave={() => { - setTooltip(null); - }} - className="ml-2" - level={realmLevel?.level as number} - /> -
-
- {showRealmLevelUp && setShowRealmLevelUp(false)}>} -
- )} -
-
setShowRealmLevelUp(true)} - onMouseEnter={() => - setTooltip({ - position: "top", - content: ( - <> -

Upgrade your Realm to unlock

-

new features and get buffs.

- - ), - }) - } - onMouseLeave={() => setTooltip(null)} - > - Level UP - -
- - -
- - ); -}; - -export default RealmInfoComponent; diff --git a/client/src/ui/components/cityview/realm/leveling/Leveling.tsx b/client/src/ui/components/cityview/realm/leveling/Leveling.tsx deleted file mode 100644 index 683eba68c..000000000 --- a/client/src/ui/components/cityview/realm/leveling/Leveling.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { useMemo } from "react"; -import useBlockchainStore from "../../../../../hooks/store/useBlockchainStore"; -import { LevelIndex, useLevel } from "../../../../../hooks/helpers/useLevel"; -import clsx from "clsx"; - -type LevelingProps = { - className?: string; - entityId: bigint | undefined; - setShowLevelUp?: (show: boolean) => void; -} & React.HTMLAttributes; - -export const Leveling = ({ className, entityId, setShowLevelUp, ...props }: LevelingProps) => { - const { getEntityLevel } = useLevel(); - - const level = entityId ? getEntityLevel(entityId) : undefined; - const nextBlockTimestamp = useBlockchainStore((state) => state.nextBlockTimestamp); - - const progress = useMemo(() => { - return ((level?.timeLeft || 0) / 604800) * 100; - }, [level, nextBlockTimestamp]); - - const timeLeftColors = useMemo(() => { - if (progress >= 66) { - return { - text: "text-order-brilliance", - bg: "!bg-order-brilliance text-order-brilliance", - container: "!bg-order-brilliance/40 text-order-brilliance/40", - }; - } - if (progress >= 33) { - return { - text: "text-order-fox", - bg: "!bg-order-fox text-order-fox", - container: "!bg-order-fox/40 text-order-fox/40", - }; - } - return { - text: "text-order-giants", - bg: "!bg-order-giants text-order-giants", - container: "!bg-order-giants/40 text-order-giants/40", - }; - }, [progress]); - - const onClick = () => { - setShowLevelUp && setShowLevelUp(true); - }; - - return ( - // mouse is pointer -
- {/* text-[13px] */} -
-
{level ? level.level : 0}
-
- - - - - -
Order LVL
-
- ); -}; - -type LevelingBonusIconsProps = { - bonuses: { bonusType: number; bonusAmount: number }[]; - includeZero?: boolean; - className?: string; -}; - -export const LevelingBonusIcons = ({ className, bonuses, includeZero }: LevelingBonusIconsProps) => { - return ( -
- {bonuses.map((bonus, i) => { - if (bonus.bonusAmount === 0 && !includeZero) return null; - if (bonus.bonusType === LevelIndex.FOOD) - return ( -
-
🌾
-
+{bonus.bonusAmount}%
-
- ); - - if (bonus.bonusType === LevelIndex.RESOURCE) - return ( -
-
💎
-
+{bonus.bonusAmount}%
-
- ); - - if (bonus.bonusType === LevelIndex.TRAVEL) - return ( -
-
🫏
-
+{bonus.bonusAmount}%
-
- ); - if (bonus.bonusType === LevelIndex.COMBAT) - return ( -
-
🛡️
-
+{bonus.bonusAmount}%
-
- ); - })} -
- ); -}; diff --git a/client/src/ui/components/cityview/realm/leveling/LevelingPopup.tsx b/client/src/ui/components/cityview/realm/leveling/LevelingPopup.tsx deleted file mode 100644 index 0dfa1866a..000000000 --- a/client/src/ui/components/cityview/realm/leveling/LevelingPopup.tsx +++ /dev/null @@ -1,306 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { SecondaryPopup } from "../../../../elements/SecondaryPopup"; -import Button from "../../../../elements/Button"; -import { Headline } from "../../../../elements/Headline"; -import { ResourceCost } from "../../../../elements/ResourceCost"; -import useRealmStore from "../../../../../hooks/store/useRealmStore"; -import { useDojo } from "../../../../../hooks/context/DojoContext"; -import { getComponentValue } from "@dojoengine/recs"; -import { divideByPrecision, getEntityIdFromKeys } from "../../../../utils/utils"; -import useUIStore from "../../../../../hooks/store/useUIStore"; -import { LevelIndex, useLevel } from "../../../../../hooks/helpers/useLevel"; -import { Resource, getLevelingCost } from "@bibliothecadao/eternum"; -import BlurryLoadingImage from "../../../../elements/BlurryLoadingImage"; -import { soundSelector, useUiSounds } from "../../../../../hooks/useUISound"; - -type LevelingPopupProps = { - onClose: () => void; -}; - -export const LevelingPopup = ({ onClose }: LevelingPopupProps) => { - const { - setup: { - components: { Resource }, - systemCalls: { level_up_realm }, - }, - account: { account }, - } = useDojo(); - - let { realmEntityId } = useRealmStore(); - - const { getEntityLevel, getRealmLevelBonus } = useLevel(); - - const [level, _] = useMemo(() => { - let level = getEntityLevel(realmEntityId)?.level || 0; - return [level, Math.floor(level / 4) + 1]; - }, [realmEntityId]); - - const bonusData = useMemo(() => { - const foodProdBonus = getRealmLevelBonus(level, LevelIndex.FOOD); - const resourceProdBonus = getRealmLevelBonus(level, LevelIndex.RESOURCE); - const travelSpeedBonus = getRealmLevelBonus(level, LevelIndex.TRAVEL); - const combatBonus = getRealmLevelBonus(level, LevelIndex.COMBAT); - return [foodProdBonus, resourceProdBonus, travelSpeedBonus, combatBonus]; - }, [level]); - - const [missingResources, setMissingResources] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const { play: playLevelUp } = useUiSounds(soundSelector.levelUp); - - const [newLevel, newIndex, newBonus] = useMemo(() => { - // don't update if click on level_up - const newLevel = level + 1; - let newIndex = newLevel % 4; - if (newIndex === 0) newIndex = 4; - - let newBonus = getRealmLevelBonus(newLevel, newIndex); - return [newLevel, newIndex, newBonus]; - }, [level]); - - // TODO: get info from contract config file - // calculate the costs of building/buying tools - let costResources = useMemo(() => { - return getLevelingCost(newLevel); - }, [realmEntityId, newLevel]); - - const onBuild = async () => { - if (realmEntityId) { - setIsLoading(true); - playLevelUp(); - await level_up_realm({ realm_entity_id: realmEntityId, signer: account }); - onClose(); - } - }; - - useEffect(() => { - let missingResources: Resource[] = []; - costResources.forEach(({ resourceId, amount }) => { - const realmResource = getComponentValue( - Resource, - getEntityIdFromKeys([BigInt(realmEntityId), BigInt(resourceId)]), - ); - - if (!realmResource || realmResource.balance < amount) { - missingResources.push({ resourceId, amount: amount - (Number(realmResource?.balance) || 0) }); - } - }); - setMissingResources(missingResources); - }, []); - - return ( - - -
-
Level up:
-
-
- -
- Level Realm to {newLevel} -
- 3 ? 3 : newLevel}.png`} - imageStyleClass="object-cover w-full rounded-[10px] h-[340px]" - > -
-
Price:
-
- {costResources.map(({ resourceId, amount }) => { - const isMissing = missingResources.find((resource) => resource.resourceId === resourceId); - return ( - - ); - })} -
-
-
- {newLevel >= 5 && ( - - )} - {newLevel < 5 && } -
-
-
-
-
-
-
- - - -
-
-
- - - ); -}; - -interface TableProps { - // Define any props for your table here - data: any[]; // Example prop for data - updateLevel: { newBonus: number; index: number }; -} - -export const LevelingTable: React.FC = ({ data, updateLevel }) => { - const setTooltip = useUIStore((state) => state.setTooltip); - - const elements = data.map((item, index) => { - return index + 1 !== updateLevel.index ? ( - {`+${Math.round(item) - 100}%`} - ) : ( - -
{`+${Math.round(item) - 100}%`}
-
{"➜"}
-
{`+${Math.round(updateLevel.newBonus - 100)}%`}
- - ); - }); - - return ( -
- - - - {/* Add your table headers here */} - - - - - - - - {/* Map through your data and create table rows */} - {elements} - -
- setTooltip({ - position: "top", - content: ( - <> -

Increase food production

- - ), - }) - } - onMouseLeave={() => { - setTooltip(null); - }} - > - Food -
- setTooltip({ - position: "top", - content: ( - <> -

Increase mines production

- - ), - }) - } - onMouseLeave={() => { - setTooltip(null); - }} - > - Mines -
- setTooltip({ - position: "top", - content: ( - <> -

Increase travel speed

- - ), - }) - } - onMouseLeave={() => { - setTooltip(null); - }} - > - Travel -
- setTooltip({ - position: "top", - content: ( - <> -

Increase combat abilities

- - ), - }) - } - onMouseLeave={() => { - setTooltip(null); - }} - > - Combat -
-
- ); -}; - -interface UnlockMessageProps { - newLevel: number; -} - -const UnlockMessage: React.FC = ({ newLevel }) => { - let title = ""; - let message = ""; - if (newLevel === 1) { - title = "Unlock Mines Production and Trading"; - message = - "Mines will allow you to produce the resources present on your realm like Coal and Wood. You can then start trading these resources with other realms."; - } else if (newLevel === 2) { - title = "Unlocking Banks"; - message = - "Banks are a key component of the world. You can send food there using caravans and swap it against LORDS following a VRGDA curve. The more food is swapped, the less LORDS you get for the same amount."; - } else if (newLevel === 3) { - title = "Unlocking Combat"; - message = - "Because of the scarcity of resources, Realms will have to fight each other to survive. Combat is a key component of the game. You can send your Raiders to attack other Realms and steal their resources. You can also defend your Realm by building a Town Watch."; - } else if (newLevel === 4) { - // title = "Unlocking Hyperstructures"; - // message = - // "Each order can build a hyperstructure by sending it resources. As the hyperstructure levels up, Realms of that order will get additional bonuses."; - } - - return ( -
- {title} - {message} -
- ); -}; diff --git a/client/src/ui/components/cityview/realm/leveling/trace b/client/src/ui/components/cityview/realm/leveling/trace deleted file mode 100644 index 60f0e1d8a..000000000 --- a/client/src/ui/components/cityview/realm/leveling/trace +++ /dev/null @@ -1,28 +0,0 @@ -2024-03-15T06:27:30.544902Z INFO txpool: Transaction received | Hash: 0x7b86936f39db95e0d5504f108bf8c98fa4727bf36bb9887f88dbbd7444d27fb -[DEBUG] 0x8 -2024-03-15T06:27:30.699256Z TRACE executor: Transaction resource usage: Steps: 703172 | Bitwise: 326 | Ec Op Builtin: 3 | L1 Gas: 21176 | Pedersen: 62 | Poseidon Builtin: 337 | Range Checks: 56096 -2024-03-15T06:27:30.699317Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699320Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699322Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699325Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699327Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699388Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699395Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699397Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699400Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699402Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699405Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699407Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699459Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699461Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699463Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699465Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699467Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699469Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699496Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699498Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699502Z TRACE executor: Event emitted keys=[0x1736c207163ad481e2a196c0fb6394f90c66c2e2b52e0c03d4a077ac6cea918, 0xc5, 0x11f, 0x3d166d0646d4dac27c1cdbed6289c386ba3ca616925438fc1334ba98241eb42] -2024-03-15T06:27:30.699505Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699507Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699510Z TRACE executor: Event emitted keys=[0x1a2f334228cee715f1f0f54053bb6b5eac54fa336e0bc1aacf7516decb0471d] -2024-03-15T06:27:30.699514Z TRACE executor: Event emitted keys=[0x2b4e2e755cbddd238cffe8dd2b9fd3b0fbb2767b1cf4f1168553431c9eb5cf0, 0x800000ee, 0x8000007e, 0xc5, 0x3d166d0646d4dac27c1cdbed6289c386ba3ca616925438fc1334ba98241eb42] \ No newline at end of file diff --git a/client/src/ui/components/construction/ExistingBuildings.tsx b/client/src/ui/components/construction/ExistingBuildings.tsx index 2fefbeb04..c7a08f73d 100644 --- a/client/src/ui/components/construction/ExistingBuildings.tsx +++ b/client/src/ui/components/construction/ExistingBuildings.tsx @@ -40,7 +40,6 @@ export const ExistingBuildings = () => { const existingBuildings = useUIStore((state) => state.existingBuildings); const setExistingBuildings = useUIStore((state) => state.setExistingBuildings); const { hexType } = useHexPosition(); - const sLightRef = useRef(); const { setup: { diff --git a/client/src/ui/components/construction/GroundGrid.tsx b/client/src/ui/components/construction/GroundGrid.tsx index a3749c581..1e1ba5d11 100644 --- a/client/src/ui/components/construction/GroundGrid.tsx +++ b/client/src/ui/components/construction/GroundGrid.tsx @@ -1,15 +1,15 @@ -import useUIStore from "../../../hooks/store/useUIStore"; -import * as THREE from "three"; -import { createHexagonShape } from "../worldmap/hexagon/HexagonGeometry"; -import { ResourceIdToMiningType, ResourceMiningTypes, getUIPositionFromColRow, pseudoRandom } from "../../utils/utils"; -import { useEffect, useMemo, useState } from "react"; -import { useBuildingSound, useShovelSound } from "../../../hooks/useUISound"; +import { useBuildings } from "@/hooks/helpers/useBuildings"; import useRealmStore from "@/hooks/store/useRealmStore"; -import { BuildingType, ResourcesIds, getNeighborHexes } from "@bibliothecadao/eternum"; import { placeholderMaterial } from "@/shaders/placeholderMaterial"; -import { Text, useGLTF } from "@react-three/drei"; -import { useBuildings } from "@/hooks/helpers/useBuildings"; import { HEX_RADIUS } from "@/ui/config"; +import { BuildingType, ResourcesIds, getNeighborHexes } from "@bibliothecadao/eternum"; +import { useGLTF } from "@react-three/drei"; +import { useEffect, useMemo, useState } from "react"; +import * as THREE from "three"; +import useUIStore from "../../../hooks/store/useUIStore"; +import { useBuildingSound, useShovelSound } from "../../../hooks/useUISound"; +import { ResourceIdToMiningType, ResourceMiningTypes, getUIPositionFromColRow, pseudoRandom } from "../../utils/utils"; +import { createHexagonShape } from "../worldmap/hexagon/HexagonGeometry"; const HEXCEPTION_CENTER = { col: 10, row: 10 }; diff --git a/client/src/ui/components/entities/Entity.tsx b/client/src/ui/components/entities/Entity.tsx index b50a1a4b6..0fe723146 100644 --- a/client/src/ui/components/entities/Entity.tsx +++ b/client/src/ui/components/entities/Entity.tsx @@ -1,7 +1,7 @@ import { useDojo } from "@/hooks/context/DojoContext"; +import { getBattleByPosition } from "@/hooks/helpers/battles/useBattles"; import { getArmyByEntityId, isArmyAlive } from "@/hooks/helpers/useArmies"; -import { getBattleByPosition } from "@/hooks/helpers/useBattles"; -import { useEntities } from "@/hooks/helpers/useEntities"; +import { useEntitiesUtils } from "@/hooks/helpers/useEntities"; import { useOwnedEntitiesOnPosition, useResources } from "@/hooks/helpers/useResources"; import useBlockchainStore from "@/hooks/store/useBlockchainStore"; import { formatSecondsLeftInDaysHours } from "@/ui/components/cityview/realm/labor/laborUtils"; @@ -9,7 +9,7 @@ import { ResourceCost } from "@/ui/elements/ResourceCost"; import { divideByPrecision } from "@/ui/utils/utils"; import { EntityState, EntityType, determineEntityState } from "@bibliothecadao/eternum"; import clsx from "clsx"; -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import { DepositResources } from "../resources/DepositResources"; import { TravelEntityPopup } from "./TravelEntityPopup"; @@ -34,11 +34,11 @@ type EntityProps = { export const Entity = ({ entityId, ...props }: EntityProps) => { const { setup: { - components: { Battle, Army, Position, Realm }, + components: { Battle, Army }, }, } = useDojo(); const [showTravel, setShowTravel] = useState(false); - const { getEntityInfo } = useEntities(); + const { getEntityInfo } = useEntitiesUtils(); const { getResourcesFromBalance } = useResources(); const { getOwnedEntityOnPosition } = useOwnedEntitiesOnPosition(); const nextBlockTimestamp = useBlockchainStore.getState().nextBlockTimestamp; @@ -47,14 +47,24 @@ export const Entity = ({ entityId, ...props }: EntityProps) => { const entity = getEntityInfo(entityId); const entityResources = getResourcesFromBalance(entityId); const hasResources = entityResources.length > 0; - const entityState = determineEntityState(nextBlockTimestamp, entity.blocked, entity.arrivalTime, hasResources); + const entityState = determineEntityState( + nextBlockTimestamp, + entity.blocked, + Number(entity.arrivalTime), + hasResources, + ); const depositEntityId = getOwnedEntityOnPosition(entityId); + const getBattle = getBattleByPosition(); - const battleAtPosition = entity?.position ? getBattleByPosition(entity.position) : undefined; - const battleInProgress = battleAtPosition && battleAtPosition.duration_left > 0; + const battleInProgress = useMemo(() => { + const battleAtPosition = entity?.position + ? getBattle({ x: Number(entity.position.x), y: Number(entity.position.y) }) + : undefined; + return battleAtPosition && battleAtPosition.duration_left > 0; + }, [entity?.position?.x, entity?.position?.y]); const army = getArmy(entityId); - if (army && !isArmyAlive(army, Battle, Army, Position, Realm)) return; + if (army && !isArmyAlive(army, Battle, Army)) return; if (entityState === EntityState.NotApplicable) return null; @@ -72,7 +82,7 @@ export const Entity = ({ entityId, ...props }: EntityProps) => { case EntityState.Traveling: return entity.arrivalTime && nextBlockTimestamp ? (
- {formatSecondsLeftInDaysHours(entity.arrivalTime - nextBlockTimestamp)} + {formatSecondsLeftInDaysHours(Number(entity.arrivalTime) - nextBlockTimestamp)}
) : null; default: @@ -98,6 +108,8 @@ export const Entity = ({ entityId, ...props }: EntityProps) => { ); }; + const name = entity.entityType === EntityType.TROOP ? army.name : entityName[entity.entityType]; + const bgColour = entityState === EntityState.Traveling ? "bg-gold/10" : "bg-green/10 animate-pulse"; return ( @@ -110,11 +122,7 @@ export const Entity = ({ entityId, ...props }: EntityProps) => {
{entityIcon[entity.entityType]} - - {entity.name === entity.entityId.toString() - ? `${entityName[entity.entityType]} ${entity.name}` - : entity.name} - + {name}
diff --git a/client/src/ui/components/entities/SelectLocationPanel.tsx b/client/src/ui/components/entities/SelectLocationPanel.tsx index 1288311fc..f3a53e73f 100644 --- a/client/src/ui/components/entities/SelectLocationPanel.tsx +++ b/client/src/ui/components/entities/SelectLocationPanel.tsx @@ -1,21 +1,15 @@ -import { useDeferredValue, useEffect, useMemo, useState } from "react"; -import { - CombatInfo, - SelectableLocationInterface, - SelectableRealmInterface, - getOrderName, -} from "@bibliothecadao/eternum"; -import { Entity, getComponentValue } from "@dojoengine/recs"; +import { useDojo } from "@/hooks/context/DojoContext"; import { useCaravan } from "@/hooks/helpers/useCaravans"; +import { useRealm } from "@/hooks/helpers/useRealm"; +import { OrderIcon } from "@/ui/elements/OrderIcon"; import { SortButton, SortInterface } from "@/ui/elements/SortButton"; -import { getRealm } from "@/ui/utils/realms"; -import { useDojo } from "@/hooks/context/DojoContext"; -import TextInput from "@/ui/elements/TextInput"; import { SortPanel } from "@/ui/elements/SortPanel"; -import { OrderIcon } from "@/ui/elements/OrderIcon"; -import { useLevel } from "@/hooks/helpers/useLevel"; -import { useRealm } from "@/hooks/helpers/useRealm"; +import TextInput from "@/ui/elements/TextInput"; +import { getRealm } from "@/ui/utils/realms"; +import { SelectableLocationInterface, getOrderName } from "@bibliothecadao/eternum"; +import { Entity, getComponentValue } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; +import { useDeferredValue, useEffect, useMemo, useState } from "react"; export const SelectLocationPanel = ({ travelingEntityId, @@ -39,9 +33,6 @@ export const SelectLocationPanel = ({ }, } = useDojo(); - // const { getDefenceOnRealm } = useCombat(); - - const { getEntityLevel } = useLevel(); const { getRealmAddressName } = useRealm(); const { calculateDistance } = useCaravan(); @@ -86,8 +77,6 @@ export const SelectLocationPanel = ({ } const entityId = realm?.entity_id || bank?.entity_id; const distance = entityId ? (calculateDistance(travelingEntityId, BigInt(entityId)) ?? 0) : 0; - // const defence = entityId ? getDefenceOnRealm(BigInt(entityId)) : undefined; - const level = entityId ? getEntityLevel(BigInt(entityId)) : undefined; const addressName = entityId ? getRealmAddressName(entityId) : ""; return { entityId, @@ -97,7 +86,6 @@ export const SelectLocationPanel = ({ order: order ? getOrderName(order) : "", distance, undefined, // defence, - level: level?.level, addressName, }; }) diff --git a/client/src/ui/components/hyperstructures/StructureCard.tsx b/client/src/ui/components/hyperstructures/StructureCard.tsx index c4029947b..4d4e6704d 100644 --- a/client/src/ui/components/hyperstructures/StructureCard.tsx +++ b/client/src/ui/components/hyperstructures/StructureCard.tsx @@ -68,8 +68,7 @@ export const StructureCard = ({ const target = formattedStructureAtPosition || formattedRealmAtPosition; return ( - Boolean(formattedStructureAtPosition) && - BigInt(target.protector?.battle_id || 0n) === 0n && ( + Boolean(formattedStructureAtPosition) && (
{!showMergeTroopsPopup && formattedRealmAtPosition && ( @@ -83,8 +82,7 @@ export const StructureCard = ({ )}
@@ -98,15 +96,9 @@ type MergeTroopsPanelProps = { giverArmy: ArmyInfo; setShowMergeTroopsPopup: (val: boolean) => void; structureEntityId: bigint; - structureName: string; }; -const MergeTroopsPanel = ({ - giverArmy, - setShowMergeTroopsPopup, - structureEntityId, - structureName, -}: MergeTroopsPanelProps) => { +const MergeTroopsPanel = ({ giverArmy, setShowMergeTroopsPopup, structureEntityId }: MergeTroopsPanelProps) => { return (
- + ) : ( <> -
-
-
-
{army.name}
+
+
+
+
+
{updatedArmy!.name}
+
+ setEditMode(!editMode)} /> + +
+
- {army.current && ( -
- HP: {(BigInt(army.current.toString()) / BigInt(1000000n)).toLocaleString()} /{" "} - {(BigInt(army.lifetime.toString()) / BigInt(1000000n)).toLocaleString()} -
- )} - +
-
- - +
+
+ + +
+ {extraButton || ""}
-
- -
- -
-
- - {extraButton || ""} )}
diff --git a/client/src/ui/components/military/ArmyList.tsx b/client/src/ui/components/military/ArmyList.tsx index cff070a5c..2c930a00d 100644 --- a/client/src/ui/components/military/ArmyList.tsx +++ b/client/src/ui/components/military/ArmyList.tsx @@ -1,6 +1,7 @@ import { useDojo } from "@/hooks/context/DojoContext"; +import { getBattleByPosition } from "@/hooks/helpers/battles/useBattles"; import { useArmiesByEntityOwner, usePositionArmies } from "@/hooks/helpers/useArmies"; -import { getBattleByPosition } from "@/hooks/helpers/useBattles"; +import { PlayerStructures } from "@/hooks/helpers/useEntities"; import { QuestName, useQuestStore } from "@/hooks/store/useQuestStore"; import Button from "@/ui/elements/Button"; import { Position } from "@bibliothecadao/eternum"; @@ -12,8 +13,10 @@ import { InventoryResources } from "../resources/InventoryResources"; import { ArmyManagementCard } from "./ArmyManagementCard"; import { ArmyViewCard } from "./ArmyViewCard"; -export const EntityArmyList = ({ structure }: any) => { - const { entityArmies } = useArmiesByEntityOwner({ entity_owner_entity_id: structure?.entity_id }); +export const EntityArmyList = ({ structure }: { structure: PlayerStructures }) => { + const { entityArmies: structureArmies } = useArmiesByEntityOwner({ + entity_owner_entity_id: structure?.entity_id || 0n, + }); const selectedQuest = useQuestStore((state) => state.selectedQuest); @@ -24,11 +27,16 @@ export const EntityArmyList = ({ structure }: any) => { }, } = useDojo(); + const getBattle = getBattleByPosition(); const [isLoading, setIsLoading] = useState(false); - const canCreateProtector = useMemo(() => !entityArmies.find((army) => army.protectee_id), [entityArmies]); - + const canCreateProtector = useMemo( + () => !structureArmies.find((army) => army.protectee?.protectee_id), + [structureArmies], + ); + console.log(structureArmies); const handleCreateArmy = (is_defensive_army: boolean) => { + if (!structure.entity_id) throw new Error("Structure's entity id is undefined"); setIsLoading(true); create_army({ signer: account, @@ -36,11 +44,11 @@ export const EntityArmyList = ({ structure }: any) => { is_defensive_army, }).finally(() => setIsLoading(false)); }; - + console.log("11"); return ( <> {" "} @@ -74,12 +82,11 @@ export const EntityArmyList = ({ structure }: any) => { title="armies" panel={({ entity }) => ( - {/* */} - + )} diff --git a/client/src/ui/components/military/ArmyManagementCard.tsx b/client/src/ui/components/military/ArmyManagementCard.tsx index f874e78ca..046963e87 100644 --- a/client/src/ui/components/military/ArmyManagementCard.tsx +++ b/client/src/ui/components/military/ArmyManagementCard.tsx @@ -1,3 +1,4 @@ +import { ReactComponent as Map } from "@/assets/icons/common/world.svg"; import { useDojo } from "@/hooks/context/DojoContext"; import { useResourceBalance } from "@/hooks/helpers/useResources"; import useBlockchainStore from "@/hooks/store/useBlockchainStore"; @@ -20,11 +21,11 @@ import { LucideArrowRight } from "lucide-react"; type ArmyManagementCardProps = { owner_entity: bigint; - entity: ArmyInfo; + army: ArmyInfo; }; // TODO Unify this. Push all useComponentValues up to the top level -export const ArmyManagementCard = ({ owner_entity, entity }: ArmyManagementCardProps) => { +export const ArmyManagementCard = ({ owner_entity, army }: ArmyManagementCardProps) => { const { account: { account }, network: { provider }, @@ -41,21 +42,28 @@ export const ArmyManagementCard = ({ owner_entity, entity }: ArmyManagementCardP const [canCreate, setCanCreate] = useState(false); // TODO: Clean this up - const position = { x: entity.x, y: entity.y }; + const armyPosition = { x: Number(army.position.x), y: Number(army.position.y) }; const isPassiveTravel = useMemo( - () => (entity.arrives_at && nextBlockTimestamp ? entity.arrives_at > nextBlockTimestamp : false), + () => + army.arrivalTime && army.arrivalTime.arrives_at && nextBlockTimestamp + ? army.arrivalTime.arrives_at > nextBlockTimestamp + : false, [nextBlockTimestamp], ); - const entityOwnerPosition = useComponentValue( + const rawEntityOwnerPosition = useComponentValue( Position, - getEntityIdFromKeys([BigInt(entity.entity_owner_id || 0)]), - ) || { x: 0, y: 0 }; + getEntityIdFromKeys([BigInt(army.entityOwner.entity_owner_id || 0)]), + ) || { + x: 0n, + y: 0n, + }; + const entityOwnerPosition = { x: Number(rawEntityOwnerPosition.x), y: Number(rawEntityOwnerPosition.y) }; const checkSamePosition = useMemo(() => { - return position.x === entityOwnerPosition.x && position.y === entityOwnerPosition.y; - }, [entityOwnerPosition, position]); + return armyPosition.x === entityOwnerPosition.x && armyPosition.y === entityOwnerPosition.y; + }, [entityOwnerPosition, armyPosition]); const [editName, setEditName] = useState(false); const [naming, setNaming] = useState(""); @@ -74,7 +82,7 @@ export const ArmyManagementCard = ({ owner_entity, entity }: ArmyManagementCardP setIsLoading(true); army_buy_troops({ signer: account, - army_id: entity.entity_id, + army_id: army.entity_id, payer_id: owner_entity, troops: { knight_count: troopCounts[ResourcesIds.Knight] * EternumGlobalConfig.resources.resourcePrecision || 0, @@ -123,7 +131,7 @@ export const ArmyManagementCard = ({ owner_entity, entity }: ArmyManagementCardP defense: 10, strong: "Paladin", weak: "Crossbowman", - current: currencyFormat(entity.troops.knight_count, 0), + current: currencyFormat(army.troops.knight_count, 0), }, { name: ResourcesIds.Crossbowman, @@ -132,7 +140,7 @@ export const ArmyManagementCard = ({ owner_entity, entity }: ArmyManagementCardP defense: 10, strong: "Knight", weak: "Paladin", - current: currencyFormat(entity.troops.crossbowman_count, 0), + current: currencyFormat(army.troops.crossbowman_count, 0), }, { name: ResourcesIds.Paladin, @@ -141,7 +149,7 @@ export const ArmyManagementCard = ({ owner_entity, entity }: ArmyManagementCardP defense: 10, strong: "Crossbowman", weak: "Knight", - current: currencyFormat(entity.troops.paladin_count, 0), + current: currencyFormat(army.troops.paladin_count, 0), }, ]; @@ -152,21 +160,21 @@ export const ArmyManagementCard = ({ owner_entity, entity }: ArmyManagementCardP travel */}
- {checkSamePosition ? "At Base " : position ? `On Map` : "Unknown"} + {checkSamePosition ? "At Base " : armyPosition ? `On Map` : "Unknown"}
{isPassiveTravel && nextBlockTimestamp ? ( <> Traveling for{" "} {isPassiveTravel - ? formatSecondsInHoursMinutes(entity.arrives_at - nextBlockTimestamp) + ? formatSecondsInHoursMinutes(Number(army.arrivalTime!.arrives_at) - nextBlockTimestamp) : "Arrives Next Tick"} ) : ( "Idle" )}
- +
{travelWindow && ( @@ -174,9 +182,9 @@ export const ArmyManagementCard = ({ owner_entity, entity }: ArmyManagementCardP setSetTravelWindow(false)} /> @@ -198,7 +206,7 @@ export const ArmyManagementCard = ({ owner_entity, entity }: ArmyManagementCardP setIsLoading(true); try { - await provider.set_entity_name({ signer: account, entity_id: entity.entity_id, name: naming }); + await provider.set_entity_name({ signer: account, entity_id: army.entity_id, name: naming }); } catch (e) { console.error(e); } @@ -211,7 +219,7 @@ export const ArmyManagementCard = ({ owner_entity, entity }: ArmyManagementCardP
) : ( -

{entity.name}

+

{army.name}

)}
- {!entity.protectee_id && entity.lifetime > 0 && ( + {!army.protectee && army.health.lifetime > 0 && (
{!isTraveling && ( @@ -422,7 +463,7 @@ export const TravelToLocation = ({ onClick={() => { travel({ signer: account, - travelling_entity_id: entity.entity_id, + travelling_entity_id: army.entity_id, destination_coord_x: handleSetTravelLocation(realm?.entity_id.toString() || "").x, destination_coord_y: handleSetTravelLocation(realm?.entity_id.toString() || "").y, }); diff --git a/client/src/ui/components/military/ArmyPanel.tsx b/client/src/ui/components/military/ArmyPanel.tsx index 6df59e86d..43ec4e3af 100644 --- a/client/src/ui/components/military/ArmyPanel.tsx +++ b/client/src/ui/components/military/ArmyPanel.tsx @@ -1,6 +1,7 @@ +import { PlayerStructures } from "@/hooks/helpers/useEntities"; import { EntityArmyList } from "./ArmyList"; -export const ArmyPanel = ({ structure }: any) => { +export const ArmyPanel = ({ structure }: { structure: PlayerStructures }) => { return (
diff --git a/client/src/ui/components/military/ArmyViewCard.tsx b/client/src/ui/components/military/ArmyViewCard.tsx index eb1e7f678..51d5427d7 100644 --- a/client/src/ui/components/military/ArmyViewCard.tsx +++ b/client/src/ui/components/military/ArmyViewCard.tsx @@ -33,8 +33,10 @@ export const ArmyViewCard = ({ >
- {army.realm.order && } - {getRealmNameById(BigInt(army.realm.realm_id))} + {army.realm && army.realm.order && ( + + )} + {getRealmNameById(BigInt(army.realm?.realm_id || 0n))}
@@ -51,7 +53,7 @@ export const ArmyViewCard = ({
HP: - {Number(army.current?.toString()) / 1000} + {Number(army.health.current.toString()) / 1000}
{/*
{army.battle_id ? army.battle_id : "No battle"} diff --git a/client/src/ui/components/military/Battle.tsx b/client/src/ui/components/military/Battle.tsx index 84dac2729..a593cc8c0 100644 --- a/client/src/ui/components/military/Battle.tsx +++ b/client/src/ui/components/military/Battle.tsx @@ -20,8 +20,7 @@ export const EnemyArmies = ({ armies, ownArmySelected }: { armies: ArmyInfo[]; o
{armies.length !== 0 && ( <> - Enemy armies -
+
{armies.map((army: ArmyInfo, index) => { const { attacker, defender } = String(army.battle_side) === "Attacker" @@ -29,9 +28,9 @@ export const EnemyArmies = ({ armies, ownArmySelected }: { armies: ArmyInfo[]; o : { attacker: ownArmySelected, defender: army }; const extraButton = ownArmySelected && !army.isMine ? ( -
+
) : undefined; diff --git a/client/src/ui/components/military/BattlesArmyTable.tsx b/client/src/ui/components/military/BattlesArmyTable.tsx index f86044367..4983091e0 100644 --- a/client/src/ui/components/military/BattlesArmyTable.tsx +++ b/client/src/ui/components/military/BattlesArmyTable.tsx @@ -5,7 +5,7 @@ import { getBattleInfoByOwnArmyEntityId, useBattleManager, usePlayerBattles, -} from "@/hooks/helpers/useBattles"; +} from "@/hooks/helpers/battles/useBattles"; import useBlockchainStore from "@/hooks/store/useBlockchainStore"; import { ResourceIcon } from "@/ui/elements/ResourceIcon"; import { currencyFormat } from "@/ui/utils/utils"; diff --git a/client/src/ui/components/military/TroopChip.tsx b/client/src/ui/components/military/TroopChip.tsx index 49e6330fd..e7aab2847 100644 --- a/client/src/ui/components/military/TroopChip.tsx +++ b/client/src/ui/components/military/TroopChip.tsx @@ -2,19 +2,19 @@ import { ArmyInfo } from "@/hooks/helpers/useArmies"; import { ResourceIcon } from "@/ui/elements/ResourceIcon"; import { currencyFormat } from "@/ui/utils/utils"; -export const TroopMenuRow = ({ army }: { army: ArmyInfo }) => { +export const TroopMenuRow = ({ army, className }: { army: ArmyInfo; className?: string }) => { return ( -
+
- +
{currencyFormat(army.troops.crossbowman_count, 0)}
- +
{currencyFormat(army.troops.knight_count, 0)}
- +
{currencyFormat(army.troops.paladin_count, 0)}
diff --git a/client/src/ui/components/models/biomes/BeachBiome.tsx b/client/src/ui/components/models/biomes/BeachBiome.tsx index 3fd01b73f..1578feacd 100644 --- a/client/src/ui/components/models/biomes/BeachBiome.tsx +++ b/client/src/ui/components/models/biomes/BeachBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; +import { pseudoRandom } from "../../../utils/utils"; type GLTFResult = GLTF & { nodes: { diff --git a/client/src/ui/components/models/biomes/DeciduousForestBiome.tsx b/client/src/ui/components/models/biomes/DeciduousForestBiome.tsx index 7a2235602..5741e867a 100644 --- a/client/src/ui/components/models/biomes/DeciduousForestBiome.tsx +++ b/client/src/ui/components/models/biomes/DeciduousForestBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; +import { pseudoRandom } from "../../../utils/utils"; type GLTFResult = GLTF & { nodes: { diff --git a/client/src/ui/components/models/biomes/DeepOceanBiome.tsx b/client/src/ui/components/models/biomes/DeepOceanBiome.tsx index 06b212f32..0550083b6 100644 --- a/client/src/ui/components/models/biomes/DeepOceanBiome.tsx +++ b/client/src/ui/components/models/biomes/DeepOceanBiome.tsx @@ -1,8 +1,7 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; +import { pseudoRandom } from "../../../utils/utils"; export function DeepOceanBiome({ hexes, zOffsets }: { hexes: any[]; zOffsets?: boolean }) { const { nodes, materials } = useGLTF("/models/biomes/deepOcean.glb") as any; diff --git a/client/src/ui/components/models/biomes/DesertBiome.tsx b/client/src/ui/components/models/biomes/DesertBiome.tsx index a2158a21e..6ad7591f2 100644 --- a/client/src/ui/components/models/biomes/DesertBiome.tsx +++ b/client/src/ui/components/models/biomes/DesertBiome.tsx @@ -1,8 +1,7 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; +import { pseudoRandom } from "../../../utils/utils"; export function DesertBiome({ hexes, zOffsets }: { hexes: any[]; zOffsets?: boolean }) { const { nodes, materials } = useGLTF("/models/biomes/desert.glb") as any; diff --git a/client/src/ui/components/models/biomes/GrasslandBiome.tsx b/client/src/ui/components/models/biomes/GrasslandBiome.tsx index d5f10ebf0..00f5baa71 100644 --- a/client/src/ui/components/models/biomes/GrasslandBiome.tsx +++ b/client/src/ui/components/models/biomes/GrasslandBiome.tsx @@ -1,8 +1,7 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; +import { pseudoRandom } from "../../../utils/utils"; export function GrasslandBiome({ hexes, zOffsets }: { hexes: any[]; zOffsets?: boolean }) { const { nodes, materials } = useGLTF("/models/biomes/grassland.glb") as any; diff --git a/client/src/ui/components/models/biomes/HexGrid.tsx b/client/src/ui/components/models/biomes/HexGrid.tsx index a652e595d..12bec007a 100644 --- a/client/src/ui/components/models/biomes/HexGrid.tsx +++ b/client/src/ui/components/models/biomes/HexGrid.tsx @@ -1,8 +1,7 @@ -import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; +import * as THREE from "three"; import { Hexagon } from "../../../../types"; +import { getUIPositionFromColRow } from "../../../utils/utils"; const hexagonGeometry = new THREE.RingGeometry(3, 2.94, 6, 1); const defaultTransform = new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(Math.PI, 0, Math.PI / 2)); diff --git a/client/src/ui/components/models/biomes/OceanBiome.tsx b/client/src/ui/components/models/biomes/OceanBiome.tsx index 3e7ef2e20..035f02c4e 100644 --- a/client/src/ui/components/models/biomes/OceanBiome.tsx +++ b/client/src/ui/components/models/biomes/OceanBiome.tsx @@ -1,8 +1,7 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; +import { pseudoRandom } from "../../../utils/utils"; export function OceanBiome({ hexes, zOffsets }: { hexes: any[]; zOffsets?: boolean }) { const { nodes, materials } = useGLTF("/models/biomes/ocean.glb") as any; diff --git a/client/src/ui/components/models/biomes/ScorchedBiome.tsx b/client/src/ui/components/models/biomes/ScorchedBiome.tsx index 222e3b06c..cf7295053 100644 --- a/client/src/ui/components/models/biomes/ScorchedBiome.tsx +++ b/client/src/ui/components/models/biomes/ScorchedBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; +import { pseudoRandom } from "../../../utils/utils"; type GLTFResult = GLTF & { nodes: { diff --git a/client/src/ui/components/models/biomes/ShrublandBiome.tsx b/client/src/ui/components/models/biomes/ShrublandBiome.tsx index 1a7ca0f5f..9ee1c2e07 100644 --- a/client/src/ui/components/models/biomes/ShrublandBiome.tsx +++ b/client/src/ui/components/models/biomes/ShrublandBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; +import { pseudoRandom } from "../../../utils/utils"; type GLTFResult = GLTF & { nodes: { diff --git a/client/src/ui/components/models/biomes/SnowBiome.tsx b/client/src/ui/components/models/biomes/SnowBiome.tsx index 6c77444af..d23ff91a3 100644 --- a/client/src/ui/components/models/biomes/SnowBiome.tsx +++ b/client/src/ui/components/models/biomes/SnowBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; +import { pseudoRandom } from "../../../utils/utils"; type SnowBiomeGLTF = GLTF & { nodes: { diff --git a/client/src/ui/components/models/biomes/SubtropicalDesertBiome.tsx b/client/src/ui/components/models/biomes/SubtropicalDesertBiome.tsx index 13b7f2bd8..d46665b8e 100644 --- a/client/src/ui/components/models/biomes/SubtropicalDesertBiome.tsx +++ b/client/src/ui/components/models/biomes/SubtropicalDesertBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; +import { pseudoRandom } from "../../../utils/utils"; type GLTFResult = GLTF & { nodes: { diff --git a/client/src/ui/components/models/biomes/TaigaBiome.tsx b/client/src/ui/components/models/biomes/TaigaBiome.tsx index 82aac7081..485f2f0e4 100644 --- a/client/src/ui/components/models/biomes/TaigaBiome.tsx +++ b/client/src/ui/components/models/biomes/TaigaBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; +import { pseudoRandom } from "../../../utils/utils"; type GLTFResult = GLTF & { nodes: { diff --git a/client/src/ui/components/models/biomes/TemperateDesertBiome.tsx b/client/src/ui/components/models/biomes/TemperateDesertBiome.tsx index de67d30d5..73abc202b 100644 --- a/client/src/ui/components/models/biomes/TemperateDesertBiome.tsx +++ b/client/src/ui/components/models/biomes/TemperateDesertBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; +import { pseudoRandom } from "../../../utils/utils"; type GLTFResult = GLTF & { nodes: { diff --git a/client/src/ui/components/models/biomes/TemperateRainforestBiome.tsx b/client/src/ui/components/models/biomes/TemperateRainforestBiome.tsx index feef77ae9..4845f1f8f 100644 --- a/client/src/ui/components/models/biomes/TemperateRainforestBiome.tsx +++ b/client/src/ui/components/models/biomes/TemperateRainforestBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; +import { pseudoRandom } from "../../../utils/utils"; type GLTFResult = GLTF & { nodes: { diff --git a/client/src/ui/components/models/biomes/TropicalRainforestBiome.tsx b/client/src/ui/components/models/biomes/TropicalRainforestBiome.tsx index 00d7540ac..f72397571 100644 --- a/client/src/ui/components/models/biomes/TropicalRainforestBiome.tsx +++ b/client/src/ui/components/models/biomes/TropicalRainforestBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; +import { pseudoRandom } from "../../../utils/utils"; type GLTFResult = GLTF & { nodes: { diff --git a/client/src/ui/components/models/biomes/TropicalSeasonalForestBiome.tsx b/client/src/ui/components/models/biomes/TropicalSeasonalForestBiome.tsx index 14b15f562..d33accd40 100644 --- a/client/src/ui/components/models/biomes/TropicalSeasonalForestBiome.tsx +++ b/client/src/ui/components/models/biomes/TropicalSeasonalForestBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; -import { Hexagon } from "../../../../types"; +import { pseudoRandom } from "../../../utils/utils"; type GLTFResult = GLTF & { nodes: { diff --git a/client/src/ui/components/models/biomes/TundraBiome.tsx b/client/src/ui/components/models/biomes/TundraBiome.tsx index 54bcd9365..b16659d5b 100644 --- a/client/src/ui/components/models/biomes/TundraBiome.tsx +++ b/client/src/ui/components/models/biomes/TundraBiome.tsx @@ -1,9 +1,8 @@ import { useGLTF } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../utils/utils"; -import * as THREE from "three"; import { useMemo } from "react"; -import { Hexagon } from "../../../../types"; +import * as THREE from "three"; import { GLTF } from "three-stdlib"; +import { pseudoRandom } from "../../../utils/utils"; type GLTFResult = GLTF & { nodes: { diff --git a/client/src/ui/components/models/buildings/worldmap/Battles.tsx b/client/src/ui/components/models/buildings/worldmap/Battles.tsx index e73002729..bd30d483e 100644 --- a/client/src/ui/components/models/buildings/worldmap/Battles.tsx +++ b/client/src/ui/components/models/buildings/worldmap/Battles.tsx @@ -1,4 +1,4 @@ -import { useBattles } from "@/hooks/helpers/useBattles"; +import { useAllBattles } from "@/hooks/helpers/battles/useBattles"; import useUIStore from "@/hooks/store/useUIStore"; import { BattleLabel } from "@/ui/components/worldmap/armies/BattleLabel"; import { getUIPositionFromColRow } from "@/ui/utils/utils"; @@ -10,8 +10,7 @@ import * as THREE from "three"; export const Battles = () => { const setSelectedBattle = useUIStore((state) => state.setSelectedBattle); - const { allBattles } = useBattles(); - const battles = allBattles(); + const battles = useAllBattles(); const onRightClick = useCallback((battle_id: any, position: Position) => { setSelectedBattle({ id: BigInt(battle_id), position }); @@ -21,7 +20,7 @@ export const Battles = () => { {battles.map((battle, index) => { if (!battle?.x || !battle?.y) return null; - const { x, y } = getUIPositionFromColRow(battle.x, battle.y, false); + const { x, y } = getUIPositionFromColRow(Number(battle.x), Number(battle.y), false); return ( { ); }; -const BattleModel = ({ battle_id, position, onClick }: { battle_id: any; position: any; onClick: () => void }) => { +const BattleModel = ({ battle_id, position, onClick }: { battle_id: bigint; position: any; onClick: () => void }) => { const selectedBattle = useUIStore((state) => state.selectedBattle); const selectedEntity = useUIStore((state) => state.selectedEntity); diff --git a/client/src/ui/components/models/buildings/worldmap/InstancedCastles.tsx b/client/src/ui/components/models/buildings/worldmap/InstancedCastles.tsx index b5a8f64fb..af702382b 100644 --- a/client/src/ui/components/models/buildings/worldmap/InstancedCastles.tsx +++ b/client/src/ui/components/models/buildings/worldmap/InstancedCastles.tsx @@ -1,10 +1,10 @@ -import { Points, useGLTF, useTexture } from "@react-three/drei"; -import { getUIPositionFromColRow, pseudoRandom } from "../../../../utils/utils"; -import * as THREE from "three"; -import { useMemo } from "react"; -import { GLTF } from "three-stdlib"; import useUIStore from "@/hooks/store/useUIStore"; import { StructureType } from "@bibliothecadao/eternum"; +import { useGLTF, useTexture } from "@react-three/drei"; +import { useMemo } from "react"; +import * as THREE from "three"; +import { GLTF } from "three-stdlib"; +import { getUIPositionFromColRow, pseudoRandom } from "../../../../utils/utils"; type GLTFResult = GLTF & { nodes: { @@ -59,7 +59,7 @@ export function InstancedCastles() { let idx = 0; let matrix = new THREE.Matrix4(); castles.forEach((castle: any) => { - const { x, y } = getUIPositionFromColRow(castle.col, castle.row, false); + const { x, y } = getUIPositionFromColRow(Number(castle.col), Number(castle.row), false); const seededRandom = pseudoRandom(x, y); matrix.makeRotationY((Math.PI / 3) * Math.floor(seededRandom * 6)); matrix.setPosition(x, 1.5, -y); diff --git a/client/src/ui/components/models/buildings/worldmap/ShardsMines.tsx b/client/src/ui/components/models/buildings/worldmap/ShardsMines.tsx index 1460ba272..e7372b50d 100644 --- a/client/src/ui/components/models/buildings/worldmap/ShardsMines.tsx +++ b/client/src/ui/components/models/buildings/worldmap/ShardsMines.tsx @@ -1,14 +1,17 @@ +import { useDojo } from "@/hooks/context/DojoContext"; import { getUIPositionFromColRow } from "@/ui/utils/utils"; -import { Billboard, useGLTF, Image, useTexture } from "@react-three/drei"; -import { useMemo } from "react"; +import { StructureType } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; import { HasValue, getComponentValue } from "@dojoengine/recs"; -import { useDojo } from "@/hooks/context/DojoContext"; +import { Billboard, Image, useGLTF, useTexture } from "@react-three/drei"; +import { useMemo } from "react"; import * as THREE from "three"; export const ShardsMines = () => { const { setup } = useDojo(); - const mines = useEntityQuery([HasValue(setup.components.Structure, { category: "FragmentMine" })]); + const mines = useEntityQuery([ + HasValue(setup.components.Structure, { category: StructureType[StructureType.FragmentMine] }), + ]); const minesPositions = useMemo(() => { return Array.from(mines).map((mine) => { @@ -16,7 +19,7 @@ export const ShardsMines = () => { return { position: position!, entityId: position?.entity_id, - uiPos: getUIPositionFromColRow(position?.x || 0, position?.y || 0, false), + uiPos: getUIPositionFromColRow(Number(position?.x || 0), Number(position?.y || 0), false), }; }); }, [mines]); diff --git a/client/src/ui/components/resources/DepositResources.tsx b/client/src/ui/components/resources/DepositResources.tsx index 69c251db1..ea083aabe 100644 --- a/client/src/ui/components/resources/DepositResources.tsx +++ b/client/src/ui/components/resources/DepositResources.tsx @@ -30,7 +30,7 @@ export const DepositResources = ({ entityId, battleInProgress }: DepositResource const entityState = determineEntityState( nextBlockTimestamp, false, - arrivalTime?.arrives_at, + Number(arrivalTime?.arrives_at || 0n), inventoryResources.length > 0, ); diff --git a/client/src/ui/components/resources/InventoryResources.tsx b/client/src/ui/components/resources/InventoryResources.tsx index 427541a77..591205d83 100644 --- a/client/src/ui/components/resources/InventoryResources.tsx +++ b/client/src/ui/components/resources/InventoryResources.tsx @@ -2,20 +2,22 @@ import { useResourceBalance, useResources } from "@/hooks/helpers/useResources"; import { ResourceCost } from "@/ui/elements/ResourceCost"; import { divideByPrecision } from "@/ui/utils/utils"; import { ResourcesIds } from "@bibliothecadao/eternum"; +import { useMemo, useState } from "react"; export const InventoryResources = ({ entityId, max = Infinity, className = "flex flex-wrap gap-1", - setShowAll, dynamic = [], + resourcesIconSize = "sm", }: { entityId: bigint; max?: number; className?: string; - setShowAll?: (showAll: boolean) => void; dynamic?: ResourcesIds[]; + resourcesIconSize?: "xs" | "sm" | "md" | "lg"; }) => { + const [showAll, setShowAll] = useState(false); const { getResourcesFromBalance } = useResources(); const { getBalance } = useResourceBalance(); @@ -25,17 +27,22 @@ export const InventoryResources = ({ getBalance(entityId, resourceId), ); + const updatedMax = useMemo(() => { + if (showAll) return Infinity; + return max; + }, [showAll, max]); + return (inventoryResources && inventoryResources.length > 0) || (dynamicResources && dynamicResources.length > 0) ? (
{dynamicResources && dynamicResources.length > 0 && dynamicResources - .slice(0, max - dynamicResources.length) + .slice(0, updatedMax - dynamicResources.length) .map( (resource) => resource && ( resource && ( - {max < inventoryResources.length && ( -
setShowAll && setShowAll(true)}>+{inventoryResources.length - max}
+ {updatedMax < inventoryResources.length && !showAll && ( +
setShowAll(true)}>+{inventoryResources.length - updatedMax}
)} - {max === Infinity && Boolean(setShowAll) &&
setShowAll!(false)}>hide
} + {showAll &&
setShowAll(false)}>hide
}
) : ( diff --git a/client/src/ui/components/trading/TradeHistoryEvent.tsx b/client/src/ui/components/trading/TradeHistoryEvent.tsx index ea141c30c..5c7cce965 100644 --- a/client/src/ui/components/trading/TradeHistoryEvent.tsx +++ b/client/src/ui/components/trading/TradeHistoryEvent.tsx @@ -1,5 +1,5 @@ import { useDojo } from "@/hooks/context/DojoContext"; -import { useEntities } from "@/hooks/helpers/useEntities"; +import { useEntitiesUtils } from "@/hooks/helpers/useEntities"; import { useResourceBalance } from "@/hooks/helpers/useResources"; import useBlockchainStore from "@/hooks/store/useBlockchainStore"; import { ResourceIcon } from "@/ui/elements/ResourceIcon"; @@ -52,7 +52,7 @@ export const TradeHistoryEvent = ({ trade }: { trade: TradeEvent }) => { } = useDojo(); const { nextBlockTimestamp } = useBlockchainStore(); - const { getAddressNameFromEntity } = useEntities(); + const { getAddressNameFromEntity } = useEntitiesUtils(); const tradeComponent = getComponentValue(Trade, getEntityIdFromKeys([trade.event.tradeId])); const eventType = trade.type === EventType.ORDER_CREATED && nextBlockTimestamp! > tradeComponent!.expires_at @@ -75,7 +75,7 @@ export const TradeHistoryEvent = ({ trade }: { trade: TradeEvent }) => { : getResourceBalance(tradeComponent!.taker_gives_resources_id); const expirationDate = - trade.type === EventType.ORDER_CREATED ? new Date(tradeComponent!.expires_at * 1000) : undefined; + trade.type === EventType.ORDER_CREATED ? new Date(Number(tradeComponent!.expires_at) * 1000) : undefined; const price = getPrice(resourceGiven[0], resourceTaken[0]); diff --git a/client/src/ui/components/worldmap/armies/Armies.tsx b/client/src/ui/components/worldmap/armies/Armies.tsx index 43aee0a62..a246e6066 100644 --- a/client/src/ui/components/worldmap/armies/Armies.tsx +++ b/client/src/ui/components/worldmap/armies/Armies.tsx @@ -1,20 +1,12 @@ // @ts-ignore -import { ArmyInfo, useArmies } from "@/hooks/helpers/useArmies"; -import { EternumGlobalConfig } from "@bibliothecadao/eternum"; +import { ArmyInfo, useMovableArmies } from "@/hooks/helpers/useArmies"; import { Army } from "./Army"; export const Armies = ({}: {}) => { - const { getArmies } = useArmies(); + const { getArmies } = useMovableArmies(); const armies = getArmies(); - // not show armies that are in a battle - const filterArmiesNotInBattle = (armies: ArmyInfo[]): ArmyInfo[] => { - return armies.filter((army: any) => { - return army.battle_id === 0n; - }); - }; - - return filterArmiesNotInBattle(armies) - .filter((army) => BigInt(army.current) / EternumGlobalConfig.troop.healthPrecision > 0) - .map((army: any) => ); + return armies + .filter((army) => BigInt(army.health.current) > 0) + .map((army: ArmyInfo) => ); }; diff --git a/client/src/ui/components/worldmap/armies/Army.tsx b/client/src/ui/components/worldmap/armies/Army.tsx index 977ba8df7..24361b71b 100644 --- a/client/src/ui/components/worldmap/armies/Army.tsx +++ b/client/src/ui/components/worldmap/armies/Army.tsx @@ -1,5 +1,5 @@ +import { getBattleByPosition } from "@/hooks/helpers/battles/useBattles"; import { ArmyInfo, getArmiesAtPosition } from "@/hooks/helpers/useArmies"; -import { getBattleByPosition } from "@/hooks/helpers/useBattles"; import { getStructureAtPosition } from "@/hooks/helpers/useStructures"; import { Billboard, Image, useTexture } from "@react-three/drei"; import React, { useMemo } from "react"; @@ -14,7 +14,7 @@ import { CombatLabel } from "./CombatLabel"; import { useArmyAnimation } from "./useArmyAnimation"; import { arePropsEqual } from "./utils"; -type ArmyProps = { +export type ArmyProps = { army: ArmyInfo; }; @@ -25,10 +25,12 @@ export const Army = React.memo(({ army }: ArmyProps & JSX.IntrinsicElements["gro texture.minFilter = THREE.LinearFilter; }); - const armyPosition = { x: army.x, y: army.y }; - // animation path for the army - const { groupRef, isAnimating } = useArmyAnimation(armyPosition, army.offset, army.isMine); + const { groupRef, isAnimating } = useArmyAnimation( + { x: Number(army.position.x), y: Number(army.position.y) }, + army.offset, + army.isMine, + ); // Deterministic rotation based on the id const deterministicRotation = useMemo(() => { @@ -62,21 +64,24 @@ export const Army = React.memo(({ army }: ArmyProps & JSX.IntrinsicElements["gro }, arePropsEqual); export const ArmySelectionOverlay = ({ army }: ArmyProps) => { - const armyPosition = { x: army.x, y: army.y }; - const selectedEntity = useUIStore((state) => state.selectedEntity); - const battleAtPosition = getBattleByPosition(armyPosition); + const getBattle = getBattleByPosition(); + + const battle = useMemo(() => getBattle(army.position), [army]); + const { getArmies } = getArmiesAtPosition(); - const structure = getStructureAtPosition(armyPosition); + const structure = getStructureAtPosition(army.position); const isSelected = useMemo(() => { return (selectedEntity?.id || 0n) === BigInt(army.entity_id); }, [selectedEntity, army.entity_id]); const showCombatLabel = useMemo(() => { - if (battleAtPosition) return false; - const { opponentArmiesAtPosition } = getArmies(armyPosition); + if (battle) return false; + + const { opponentArmiesAtPosition } = getArmies(army.position); + return ( selectedEntity !== undefined && selectedEntity.id === BigInt(army.entity_id) && @@ -85,14 +90,14 @@ export const ArmySelectionOverlay = ({ army }: ArmyProps) => { }, [selectedEntity]); const showBattleLabel = useMemo(() => { - return selectedEntity !== undefined && selectedEntity.id === BigInt(army.entity_id) && Boolean(battleAtPosition); - }, [selectedEntity, battleAtPosition]); + return selectedEntity !== undefined && selectedEntity.id === BigInt(army.entity_id) && Boolean(battle); + }, [selectedEntity, battle]); return ( <> {showCombatLabel && } - {showBattleLabel && } - {isSelected && } + {showBattleLabel && } + {isSelected && } ); }; diff --git a/client/src/ui/components/worldmap/armies/ArmyHitBox.tsx b/client/src/ui/components/worldmap/armies/ArmyHitBox.tsx index b93c04a5a..5d31a4231 100644 --- a/client/src/ui/components/worldmap/armies/ArmyHitBox.tsx +++ b/client/src/ui/components/worldmap/armies/ArmyHitBox.tsx @@ -27,10 +27,22 @@ export const ArmyHitBox = ({ army, hovered, isAnimating, setHovered }: ArmyHitBo const onRightClick = useCallback(() => { playClick(); if ((selectedEntity?.id || 0n) !== BigInt(army.entity_id) && army.isMine && !isAnimating) { - setSelectedEntity({ id: BigInt(army.entity_id), position: { x: army.x, y: army.y } }); + setSelectedEntity({ + id: BigInt(army.entity_id), + position: { x: Number(army.position.x), y: Number(army.position.y) }, + }); playSelectedArmy(); } - }, [isAnimating, army.entity_id, army.x, army.y, selectedEntity, setSelectedEntity, playSelectedArmy, playClick]); + }, [ + isAnimating, + army.entity_id, + army.position.x, + army.position.y, + selectedEntity, + setSelectedEntity, + playSelectedArmy, + playClick, + ]); const onPointerEnter = useCallback( (e: any) => { diff --git a/client/src/ui/components/worldmap/armies/ArmyInfoLabel.tsx b/client/src/ui/components/worldmap/armies/ArmyInfoLabel.tsx index a7914da34..a300c909e 100644 --- a/client/src/ui/components/worldmap/armies/ArmyInfoLabel.tsx +++ b/client/src/ui/components/worldmap/armies/ArmyInfoLabel.tsx @@ -4,16 +4,15 @@ import { currencyFormat } from "../../../utils/utils"; import { useDojo } from "@/hooks/context/DojoContext"; import { ArmyInfo } from "@/hooks/helpers/useArmies"; import { BaseThreeTooltip, Position } from "@/ui/elements/BaseThreeTooltip"; +import { Headline } from "@/ui/elements/Headline"; import { ResourceIcon } from "@/ui/elements/ResourceIcon"; import { StaminaResource } from "@/ui/elements/StaminaResource"; import clsx from "clsx"; import { useMemo } from "react"; import { useRealm } from "../../../../hooks/helpers/useRealm"; -import { OrderIcon } from "../../../elements/OrderIcon"; -import { getRealmNameById, getRealmOrderNameById } from "../../../utils/realms"; +import { getRealmNameById } from "../../../utils/realms"; import { formatSecondsLeftInDaysHours } from "../../cityview/realm/labor/laborUtils"; import { InventoryResources } from "../../resources/InventoryResources"; -import { Headline } from "@/ui/elements/Headline"; interface ArmyInfoLabelProps { army: ArmyInfo; @@ -38,22 +37,19 @@ interface ArmyInfoLabelProps { } const RaiderInfo = ({ army }: ArmyInfoLabelProps) => { - const { - account: { account }, - } = useDojo(); - const { getRealmAddressName } = useRealm(); const nextBlockTimestamp = useBlockchainStore.getState().nextBlockTimestamp as number; - const { entity_id, entity_owner_id, address, arrives_at, realm, troops, battle_id } = army; + const { realm, entity_id, entityOwner, troops, arrivalTime } = army; const isPassiveTravel = useMemo( - () => (army.arrives_at && nextBlockTimestamp ? army.arrives_at > nextBlockTimestamp : false), - [army.arrives_at, nextBlockTimestamp], + () => + arrivalTime && arrivalTime.arrives_at && nextBlockTimestamp ? arrivalTime.arrives_at > nextBlockTimestamp : false, + [arrivalTime?.arrives_at, nextBlockTimestamp], ); const realmId = BigInt(realm?.realm_id || 0); - const attackerAddressName = entity_owner_id ? getRealmAddressName(BigInt(entity_owner_id)) : ""; + const attackerAddressName = entityOwner ? getRealmAddressName(BigInt(entityOwner.entity_owner_id)) : ""; const originRealmName = getRealmNameById(BigInt(realmId)); @@ -63,7 +59,7 @@ const RaiderInfo = ({ army }: ArmyInfoLabelProps) => {
@@ -82,10 +78,12 @@ const RaiderInfo = ({ army }: ArmyInfoLabelProps) => {
{!isTraveling &&
Idle
} - {army.arrives_at && isTraveling && nextBlockTimestamp && ( + {arrivalTime && arrivalTime.arrives_at !== undefined && isTraveling && nextBlockTimestamp && (
- {isPassiveTravel ? formatSecondsLeftInDaysHours(arrives_at - nextBlockTimestamp) : "Arrives Next Tick"} - {battle_id && `In Battle`} + {isPassiveTravel + ? formatSecondsLeftInDaysHours(Number(arrivalTime.arrives_at) - nextBlockTimestamp) + : "Arrives Next Tick"} + {army.battle_id ? `In Battle` : ""}
)} diff --git a/client/src/ui/components/worldmap/armies/BattleLabel.tsx b/client/src/ui/components/worldmap/armies/BattleLabel.tsx index 6de09b916..4f15d0959 100644 --- a/client/src/ui/components/worldmap/armies/BattleLabel.tsx +++ b/client/src/ui/components/worldmap/armies/BattleLabel.tsx @@ -24,7 +24,7 @@ export const BattleLabel = ({ selectedBattle, visible = true }: BattleLabelProps const position = getComponentValue(Position, getEntityIdFromKeys([selectedBattle])); setSelectedBattle(undefined); setBattleView({ - battle: { x: position!.x, y: position!.y }, + battle: { x: Number(position!.x), y: Number(position!.y) }, target: undefined, }); }; diff --git a/client/src/ui/components/worldmap/armies/utils.tsx b/client/src/ui/components/worldmap/armies/utils.tsx index 9fc3b0dda..a349166c8 100644 --- a/client/src/ui/components/worldmap/armies/utils.tsx +++ b/client/src/ui/components/worldmap/armies/utils.tsx @@ -1,4 +1,5 @@ import { UIPosition } from "@bibliothecadao/eternum"; +import { ArmyProps } from "./Army"; export const applyOffset = (point: UIPosition, offset: { x: number; y: number }) => ({ x: point.x + offset.x, @@ -7,8 +8,10 @@ export const applyOffset = (point: UIPosition, offset: { x: number; y: number }) }); export const arePropsEqual = ( - prevProps: any & JSX.IntrinsicElements["group"], - nextProps: any & JSX.IntrinsicElements["group"], + prevProps: ArmyProps & JSX.IntrinsicElements["group"], + nextProps: ArmyProps & JSX.IntrinsicElements["group"], ) => { - return prevProps.army.x === nextProps.army.x && prevProps.army.y === nextProps.army.y; + return ( + prevProps.army.position.x === nextProps.army.position.x && prevProps.army.position.y === nextProps.army.position.y + ); }; diff --git a/client/src/ui/components/worldmap/hexagon/HexagonInformationPanel.tsx b/client/src/ui/components/worldmap/hexagon/HexagonInformationPanel.tsx deleted file mode 100644 index 4b0e937e6..000000000 --- a/client/src/ui/components/worldmap/hexagon/HexagonInformationPanel.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { ArmyInfo, usePositionArmies } from "@/hooks/helpers/useArmies"; -import { BattleInfo, useBattlesByPosition } from "@/hooks/helpers/useBattles"; -import { Structure, useStructuresPosition } from "@/hooks/helpers/useStructures"; -import useUIStore from "@/hooks/store/useUIStore"; -import { ClickedHex } from "@/types"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/ui/elements/Select"; -import { Position } from "@bibliothecadao/eternum"; -import { useMemo, useState } from "react"; -import { StructureCard } from "../../hyperstructures/StructureCard"; -import { EnemyArmies } from "../../military/Battle"; - -type ToShow = { - showSelectableUnits: boolean; - showEnnemies: boolean; - showStructure: boolean; -}; - -export const HexagonInformationPanel = () => { - const clickedHex = useUIStore((state) => state.clickedHex); - const selectedEntity = useUIStore((state) => state.selectedEntity); - const [ownArmySelected, setOwnArmySelected] = useState(selectedEntity); - - const hexPosition = useMemo(() => { - if (selectedEntity) return { x: selectedEntity.position.x, y: selectedEntity.position.y }; - if (clickedHex) return { x: clickedHex.contractPos.col, y: clickedHex.contractPos.row }; - }, [clickedHex, selectedEntity]); - - const battle = useBattlesByPosition(hexPosition || { x: 0, y: 0 }); - - const { useFormattedStructureAtPosition } = useStructuresPosition({ position: hexPosition || { x: 0, y: 0 } }); - const formattedStructureAtPosition = useFormattedStructureAtPosition(); - - const { userAttackingArmies, enemyArmies } = usePositionArmies({ - position: { x: hexPosition?.x || 0, y: hexPosition?.y || 0 }, - }); - - const panelSelectedEntity = useMemo(() => { - if (selectedEntity) return selectedEntity; - if (userAttackingArmies.length > 0 && clickedHex && enemyArmies.length > 0) { - const entity = { - id: BigInt(userAttackingArmies[0].entity_id), - position: { x: clickedHex.contractPos.col, y: clickedHex.contractPos.row }, - }; - setOwnArmySelected(entity); - return entity; - } - }, [clickedHex]); - - const ownArmy = useMemo(() => { - if (!ownArmySelected) return; - return userAttackingArmies.find((army) => BigInt(army.entity_id) === ownArmySelected.id); - }, [userAttackingArmies, selectedEntity]); - const toShow = checkWhatToShow( - battle, - ownArmySelected, - clickedHex, - userAttackingArmies, - enemyArmies, - formattedStructureAtPosition, - ); - - return ( - hexPosition && ( -
- {/* */} - {toShow.showSelectableUnits && ( - - )} - {toShow.showStructure && } - {toShow.showEnnemies && } - {!toShow.showEnnemies && !toShow.showStructure && "Nothing to show here"} -
- ) - ); -}; - -export default HexagonInformationPanel; - -const Coordinates = ({ position }: { position: Position }) => { - return ( -
-
Coordinates
-
-
{`x: ${position.x?.toLocaleString()}`}
-
{`y: ${position.y?.toLocaleString()}`}
-
-
- ); -}; - -const SelectActiveArmy = ({ - selectedEntity, - setOwnArmySelected, - userAttackingArmies, -}: { - selectedEntity: - | { - id: bigint; - position: Position; - } - | undefined; - setOwnArmySelected: (val: any) => void; - userAttackingArmies: ArmyInfo[]; -}) => { - return ( -
- -
- ); -}; - -const checkWhatToShow = ( - battle: BattleInfo | undefined, - selectedEntity: - | { - id: bigint; - position: Position; - } - | undefined, - clickedHex: ClickedHex | undefined, - userAttackingArmies: ArmyInfo[], - enemyArmies: ArmyInfo[], - structure: Structure | undefined, -): ToShow => { - if (battle) { - return { - showSelectableUnits: false, - showEnnemies: false, - showStructure: false, - }; - } else { - if (selectedEntity) { - if (structure) { - return { - showSelectableUnits: true, - showEnnemies: false, - showStructure: true && Boolean(structure), - }; - } else { - return { - showSelectableUnits: true, - showEnnemies: true && enemyArmies.length > 0, - showStructure: false, - }; - } - } else { - if (clickedHex && userAttackingArmies.length > 0) { - if (structure) { - return { - showSelectableUnits: true, - showEnnemies: false, - showStructure: true && Boolean(structure), - }; - } else { - return { - showSelectableUnits: true, - showEnnemies: true && enemyArmies.length > 0, - showStructure: false, - }; - } - } else { - return { - showSelectableUnits: false, - showEnnemies: true && enemyArmies.length > 0, - showStructure: true && Boolean(structure), - }; - } - } - } -}; diff --git a/client/src/ui/components/worldmap/realms/RealmListItem.tsx b/client/src/ui/components/worldmap/realms/RealmListItem.tsx index fa7d69675..0f7686337 100644 --- a/client/src/ui/components/worldmap/realms/RealmListItem.tsx +++ b/client/src/ui/components/worldmap/realms/RealmListItem.tsx @@ -6,17 +6,13 @@ import { RealmExtended } from "../../../../hooks/helpers/useRealm"; import { OrderIcon } from "../../../elements/OrderIcon"; import { ResourceIcon } from "../../../elements/ResourceIcon"; import { InventoryResources } from "../../resources/InventoryResources"; -import { useState } from "react"; type RealmListItemProps = { realm: Realm | RealmExtended; - onClick?: () => void; extraButton?: JSX.Element; }; -export const RealmListItem = ({ realm, onClick, extraButton }: RealmListItemProps) => { - const [showAll, setShowAll] = useState(false); - +export const RealmListItem = ({ realm, extraButton }: RealmListItemProps) => { return (
@@ -45,12 +41,7 @@ export const RealmListItem = ({ realm, onClick, extraButton }: RealmListItemProp
Inventory
- + {extraButton || ""}
); diff --git a/client/src/ui/components/worldmap/structures/StructureListItem.tsx b/client/src/ui/components/worldmap/structures/StructureListItem.tsx index 0f8647692..36263ccb0 100644 --- a/client/src/ui/components/worldmap/structures/StructureListItem.tsx +++ b/client/src/ui/components/worldmap/structures/StructureListItem.tsx @@ -32,7 +32,7 @@ export const StructureListItem = ({ structure, extraButton }: StructureListItemP {structure.name}
-
Owner:
{addressName} +
Owner:
{addressName === "" ? "Bandits" : addressName}
{String(structure.category) === "FragmentMine" ? ( diff --git a/client/src/ui/elements/RealmLevel.tsx b/client/src/ui/elements/RealmLevel.tsx deleted file mode 100644 index 01f8a8651..000000000 --- a/client/src/ui/elements/RealmLevel.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import clsx from "clsx"; -import React from "react"; - -type RealmLevelProps = { - level: number; -} & React.ComponentPropsWithRef<"div">; - -export const RealmLevel = ({ level, className, ...props }: RealmLevelProps) => ( -
- LVL {level} -
-); diff --git a/client/src/ui/modules/entity-details/EntityDetails.tsx b/client/src/ui/modules/entity-details/EntityDetails.tsx index a7f543856..0dfe2737a 100644 --- a/client/src/ui/modules/entity-details/EntityDetails.tsx +++ b/client/src/ui/modules/entity-details/EntityDetails.tsx @@ -1,9 +1,189 @@ -import HexagonInformationPanel from "@/ui/components/worldmap/hexagon/HexagonInformationPanel"; +import { BattleInfo, useBattlesByPosition } from "@/hooks/helpers/battles/useBattles"; +import { ArmyInfo, usePositionArmies } from "@/hooks/helpers/useArmies"; +import { Structure, useStructuresPosition } from "@/hooks/helpers/useStructures"; +import useUIStore from "@/hooks/store/useUIStore"; +import { ClickedHex } from "@/types"; +import { StructureCard } from "@/ui/components/hyperstructures/StructureCard"; +import { EnemyArmies } from "@/ui/components/military/Battle"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/ui/elements/Select"; +import { Position } from "@bibliothecadao/eternum"; +import { useMemo, useState } from "react"; + +type ToShow = { + showSelectableUnits: boolean; + showEnnemies: boolean; + showStructure: boolean; +}; export const EntityDetails = () => { + const clickedHex = useUIStore((state) => state.clickedHex); + const selectedEntity = useUIStore((state) => state.selectedEntity); + + const [ownArmySelected, setOwnArmySelected] = useState(selectedEntity); + + const hexPosition = useMemo(() => { + if (selectedEntity) return { x: selectedEntity.position.x, y: selectedEntity.position.y }; + if (clickedHex) return { x: clickedHex.contractPos.col, y: clickedHex.contractPos.row }; + }, [clickedHex, selectedEntity]); + + const battle = useBattlesByPosition(hexPosition || { x: 0, y: 0 }); + + const { useFormattedStructureAtPosition } = useStructuresPosition({ position: hexPosition || { x: 0, y: 0 } }); + const formattedStructureAtPosition = useFormattedStructureAtPosition(); + + const { userAttackingArmies, enemyArmies } = usePositionArmies({ + position: { x: hexPosition?.x || 0, y: hexPosition?.y || 0 }, + }); + + const panelSelectedEntity = useMemo(() => { + if (selectedEntity) return selectedEntity; + if (userAttackingArmies.length > 0 && clickedHex && enemyArmies.length > 0) { + const entity = { + id: BigInt(userAttackingArmies[0].entity_id), + position: { x: clickedHex.contractPos.col, y: clickedHex.contractPos.row }, + }; + setOwnArmySelected(entity); + return entity; + } + }, [clickedHex]); + + const ownArmy = useMemo(() => { + if (!ownArmySelected) return; + return userAttackingArmies.find((army) => BigInt(army.entity_id) === ownArmySelected.id); + }, [userAttackingArmies, selectedEntity]); + const toShow = checkWhatToShow( + battle, + ownArmySelected, + clickedHex, + userAttackingArmies, + enemyArmies, + formattedStructureAtPosition, + ); + return ( - <> - - + hexPosition && ( +
+ + {toShow.showSelectableUnits && ( + + )} + {toShow.showStructure && } + {toShow.showEnnemies && } + {!toShow.showEnnemies && !toShow.showStructure && "Nothing to show here"} +
+ ) ); }; + +const Coordinates = ({ position }: { position: Position }) => { + return ( +
+
Coordinates
+
+
{`x: ${position.x?.toLocaleString()}`}
+
{`y: ${position.y?.toLocaleString()}`}
+
+
+ ); +}; + +const SelectActiveArmy = ({ + selectedEntity, + setOwnArmySelected, + userAttackingArmies, +}: { + selectedEntity: + | { + id: bigint; + position: Position; + } + | undefined; + setOwnArmySelected: (val: any) => void; + userAttackingArmies: ArmyInfo[]; +}) => { + return ( +
+ +
+ ); +}; + +const checkWhatToShow = ( + battle: BattleInfo | undefined, + selectedEntity: + | { + id: bigint; + position: Position; + } + | undefined, + clickedHex: ClickedHex | undefined, + userAttackingArmies: ArmyInfo[], + enemyArmies: ArmyInfo[], + structure: Structure | undefined, +): ToShow => { + if (battle) { + return { + showSelectableUnits: false, + showEnnemies: false, + showStructure: false, + }; + } else { + if (selectedEntity) { + if (structure) { + return { + showSelectableUnits: true, + showEnnemies: false, + showStructure: true && Boolean(structure), + }; + } else { + return { + showSelectableUnits: true, + showEnnemies: true && enemyArmies.length > 0, + showStructure: false, + }; + } + } else { + if (clickedHex && userAttackingArmies.length > 0) { + if (structure) { + return { + showSelectableUnits: true, + showEnnemies: false, + showStructure: true && Boolean(structure), + }; + } else { + return { + showSelectableUnits: true, + showEnnemies: true && enemyArmies.length > 0, + showStructure: false, + }; + } + } else { + return { + showSelectableUnits: false, + showEnnemies: true && enemyArmies.length > 0, + showStructure: true && Boolean(structure), + }; + } + } + } +}; diff --git a/client/src/ui/modules/military/Military.tsx b/client/src/ui/modules/military/Military.tsx index b4c8d08ac..b4859f5ec 100644 --- a/client/src/ui/modules/military/Military.tsx +++ b/client/src/ui/modules/military/Military.tsx @@ -2,16 +2,12 @@ import { useEntities } from "@/hooks/helpers/useEntities"; import { HintSection } from "@/ui/components/hints/HintModal"; import { EntityList } from "@/ui/components/list/EntityList"; import { ArmyPanel } from "@/ui/components/military/ArmyPanel"; -import { BattlesArmyTable } from "@/ui/components/military/BattlesArmyTable"; import { EntitiesArmyTable } from "@/ui/components/military/EntitiesArmyTable"; import { HintModalButton } from "@/ui/elements/HintModalButton"; -import { Tabs } from "@/ui/elements/tab"; -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { useLocation } from "wouter"; export const Military = ({ entityId }: { entityId: bigint | undefined }) => { - const [selectedTab, setSelectedTab] = useState(0); - const { playerStructures } = useEntities(); const [location, _] = useLocation(); @@ -20,51 +16,11 @@ export const Military = ({ entityId }: { entityId: bigint | undefined }) => { return location === "/map"; }, [location]); - const tabs = useMemo( - () => [ - { - key: "Armies", - label: ( -
-
Armies
-
- ), - component: , - }, - { - key: "Battles", - label: ( -
-
Battles
-
- ), - component: , - }, - ], - [selectedTab], - ); - return (
{isMap ? ( - setSelectedTab(index)} - variant="default" - className="" - > - - {tabs.map((tab, index) => ( - {tab.label} - ))} - - - {tabs.map((tab, index) => ( - {tab.component} - ))} - - + ) : ( | undefined; + battleAdjusted: ComponentValue | undefined; attackerArmies: ArmyInfo[]; attackerHealth: Health; attackerTroops: Troops; @@ -100,12 +100,13 @@ export const Battle = ({ /> {showBattleDetails && battleAdjusted ? ( ) : ( | undefined; + battle: ComponentValue | undefined; isActive: boolean; }) => { - const [localSelectedUnit, setLocalSelectedUnit] = useState(ownArmyEntityId); + const [localSelectedUnit, setLocalSelectedUnit] = useState( + ownArmyEntityId || BigInt(userArmiesAtPosition?.[0]?.entity_id || 0), + ); const [loading, setLoading] = useState(Loading.None); const setBattleView = useUIStore((state) => state.setBattleView); @@ -93,7 +96,7 @@ export const BattleActions = ({ , ); @@ -107,7 +110,10 @@ export const BattleActions = ({ defending_army_id: defender!.entity_id, }); setLoading(Loading.None); - setBattleView({ battle: { x: selectedArmy!.x || 0, y: selectedArmy!.y || 0 }, target: undefined }); + setBattleView({ + battle: { x: Number(selectedArmy!.position.x), y: Number(selectedArmy!.position.y) }, + target: undefined, + }); clearSelection(); }; @@ -199,7 +205,7 @@ export const BattleActions = ({ loading !== Loading.None || !selectedArmy || !Boolean(selectedArmy.battle_id) || - (isActive && selectedArmy.protectee_id !== undefined) + (isActive && selectedArmy.protectee !== undefined) } > coin @@ -271,13 +277,12 @@ const ArmySelector = ({ }; const checkIfArmyLostAFinishedBattle = (battle: any, army: any, isActive: boolean) => { - if (battle && armyIsLosingSide(army, battle!) && !isActive) { + if (battle && armyHasLost(army, battle!) && !isActive) { return true; } return false; }; const checkIfArmyAlive = (army: ArmyInfo) => { - if (army.current === undefined) return false; - return BigInt(army.current) / EternumGlobalConfig.troop.healthPrecision > 0; + return BigInt(army.health.current) > 0; }; diff --git a/client/src/ui/modules/military/battle-view/BattleDetails.tsx b/client/src/ui/modules/military/battle-view/BattleDetails.tsx index 950742403..000644fe8 100644 --- a/client/src/ui/modules/military/battle-view/BattleDetails.tsx +++ b/client/src/ui/modules/military/battle-view/BattleDetails.tsx @@ -46,9 +46,9 @@ export const BattleDetails = ({ battleId, armies }: { battleId: bigint; armies: {army.name}
- {army.address ? getAddressName(String(army.address)) : "Mercenaries"} + {army.entityOwner ? getAddressName(String(army.owner.address)) : "Bandits"}
- {army.isMine && BigInt(army.protectee_id || 0) === 0n ? ( + {army.isMine && BigInt(army.protectee?.protectee_id || 0) === 0n ? (