From 98b2fcab4792a32fe8b137ec43d0c9ac7be6e3ac Mon Sep 17 00:00:00 2001 From: raschel <38816784+aymericdelab@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:57:52 +0200 Subject: [PATCH] Hyperstructure contributions summary (#1592) * change fucntion name * added contribution summary --- .../dojo/modelManager/LeaderboardManager.ts | 2 +- .../__tests__/LeaderboardManager.test.ts | 8 +- client/src/hooks/helpers/useContributions.tsx | 18 ++-- .../src/hooks/helpers/useHyperstructures.tsx | 9 +- .../hyperstructures/ContributionSummary.tsx | 90 +++++++++++++++++++ .../hyperstructures/HyperstructurePanel.tsx | 84 ++++++++--------- .../HyperstructureResourceChip.tsx | 18 ++-- .../ui/components/military/PillageHistory.tsx | 11 +-- client/src/ui/elements/ResourceIcon.tsx | 2 +- .../world-structures/WorldStructuresMenu.tsx | 4 +- client/src/ui/utils/utils.tsx | 9 ++ 11 files changed, 183 insertions(+), 72 deletions(-) create mode 100644 client/src/ui/components/hyperstructures/ContributionSummary.tsx diff --git a/client/src/dojo/modelManager/LeaderboardManager.ts b/client/src/dojo/modelManager/LeaderboardManager.ts index 431c8fc24..5fa88072e 100644 --- a/client/src/dojo/modelManager/LeaderboardManager.ts +++ b/client/src/dojo/modelManager/LeaderboardManager.ts @@ -68,7 +68,7 @@ export class LeaderboardManager { return Array.from(pointsPerPlayer).sort(([_A, playerA], [_B, playerB]) => playerB - playerA); } - public getShares(playerAddress: ContractAddress, hyperstructureEntityId: ID) { + public getAddressShares(playerAddress: ContractAddress, hyperstructureEntityId: ID) { const lastChangeEvent = this.eventsCoOwnersChange.findLast( (event) => event.hyperstructureEntityId === hyperstructureEntityId, ); diff --git a/client/src/dojo/modelManager/__tests__/LeaderboardManager.test.ts b/client/src/dojo/modelManager/__tests__/LeaderboardManager.test.ts index 72949238d..467bdd876 100644 --- a/client/src/dojo/modelManager/__tests__/LeaderboardManager.test.ts +++ b/client/src/dojo/modelManager/__tests__/LeaderboardManager.test.ts @@ -229,21 +229,23 @@ describe("processHyperstructureCoOwnersChangeEvent", () => { describe("getShares", () => { it("should return undefined if no change co owner event occured", () => { - expect(leaderboardManager.getShares(OWNER_1_ADDRESS, HYPERSTRUCTURE_ENTITY_ID)).toBeUndefined(); + expect(leaderboardManager.getAddressShares(OWNER_1_ADDRESS, HYPERSTRUCTURE_ENTITY_ID)).toBeUndefined(); }); it("should return undefined if an event occured but not for the right entity id", () => { const event = generateMockCoOwnersChangeEvent(HYPERSTRUCTURE_ENTITY_ID); leaderboardManager.processHyperstructureCoOwnersChangeEvent(event); - expect(leaderboardManager.getShares(OWNER_1_ADDRESS, HYPERSTRUCTURE_ENTITY_ID + 1)).toBeUndefined(); + expect(leaderboardManager.getAddressShares(OWNER_1_ADDRESS, HYPERSTRUCTURE_ENTITY_ID + 1)).toBeUndefined(); }); it("should return the correct amount of shares", () => { const event = generateMockCoOwnersChangeEvent(HYPERSTRUCTURE_ENTITY_ID); leaderboardManager.processHyperstructureCoOwnersChangeEvent(event); - expect(leaderboardManager.getShares(OWNER_1_ADDRESS, HYPERSTRUCTURE_ENTITY_ID)).toBe(OWNER_1_SHARES / 10_000); + expect(leaderboardManager.getAddressShares(OWNER_1_ADDRESS, HYPERSTRUCTURE_ENTITY_ID)).toBe( + OWNER_1_SHARES / 10_000, + ); }); }); diff --git a/client/src/hooks/helpers/useContributions.tsx b/client/src/hooks/helpers/useContributions.tsx index b0c954516..0e9675e6b 100644 --- a/client/src/hooks/helpers/useContributions.tsx +++ b/client/src/hooks/helpers/useContributions.tsx @@ -1,8 +1,9 @@ -import { ContractAddress, ID } from "@bibliothecadao/eternum"; +import { ClientComponents } from "@/dojo/createClientComponents"; +import { getTotalPointsPercentage } from "@/dojo/modelManager/utils/LeaderboardUtils"; +import { ContractAddress, ID, Resource } from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; import { ComponentValue, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; import { useDojo } from "../context/DojoContext"; -import { ClientComponents } from "@/dojo/createClientComponents"; export const useContributions = () => { const { @@ -16,10 +17,10 @@ export const useContributions = () => { runQuery([HasValue(Contribution, { hyperstructure_entity_id: hyperstructureEntityId })]), ).map((id) => getComponentValue(Contribution, id)); - return contributionsToHyperstructure; + return contributionsToHyperstructure as ComponentValue[]; }; - const getContributionsByPlayerAddress = (playerAddress: ContractAddress, hyperstructureEntityId: ID) => { + const useContributionsByPlayerAddress = (playerAddress: ContractAddress, hyperstructureEntityId: ID) => { const contributionsToHyperstructure = useEntityQuery([ HasValue(Contribution, { hyperstructure_entity_id: hyperstructureEntityId, player_address: playerAddress }), ]) @@ -29,8 +30,15 @@ export const useContributions = () => { return contributionsToHyperstructure; }; + const getContributionsTotalPercentage = (contributions: Resource[]) => { + return contributions.reduce((acc, { resourceId, amount }) => { + return acc + getTotalPointsPercentage(resourceId, BigInt(amount)); + }, 0); + }; + return { getContributions, - getContributionsByPlayerAddress, + useContributionsByPlayerAddress, + getContributionsTotalPercentage, }; }; diff --git a/client/src/hooks/helpers/useHyperstructures.tsx b/client/src/hooks/helpers/useHyperstructures.tsx index 1c8e85bf5..635486fa5 100644 --- a/client/src/hooks/helpers/useHyperstructures.tsx +++ b/client/src/hooks/helpers/useHyperstructures.tsx @@ -1,7 +1,12 @@ import { ClientComponents } from "@/dojo/createClientComponents"; -import { HyperstructureResourceMultipliers } from "@bibliothecadao/eternum"; import { TOTAL_CONTRIBUTABLE_AMOUNT } from "@/dojo/modelManager/utils/LeaderboardUtils"; -import { EternumGlobalConfig, HYPERSTRUCTURE_TOTAL_COSTS_SCALED, ID, ResourcesIds } from "@bibliothecadao/eternum"; +import { + EternumGlobalConfig, + HYPERSTRUCTURE_TOTAL_COSTS_SCALED, + HyperstructureResourceMultipliers, + ID, + ResourcesIds, +} from "@bibliothecadao/eternum"; import { useEntityQuery } from "@dojoengine/react"; import { Component, ComponentValue, Entity, Has, HasValue, getComponentValue, runQuery } from "@dojoengine/recs"; import { toInteger } from "lodash"; diff --git a/client/src/ui/components/hyperstructures/ContributionSummary.tsx b/client/src/ui/components/hyperstructures/ContributionSummary.tsx new file mode 100644 index 000000000..32a3ac77e --- /dev/null +++ b/client/src/ui/components/hyperstructures/ContributionSummary.tsx @@ -0,0 +1,90 @@ +import { useContributions } from "@/hooks/helpers/useContributions"; +import { useRealm } from "@/hooks/helpers/useRealm"; +import { ResourceIcon } from "@/ui/elements/ResourceIcon"; +import { currencyIntlFormat, divideByPrecision } from "@/ui/utils/utils"; +import { ContractAddress, ID, ResourcesIds } from "@bibliothecadao/eternum"; +import { useState } from "react"; + +export const ContributionSummary = ({ + hyperstructureEntityId, + className, +}: { + hyperstructureEntityId: ID; + className?: string; +}) => { + const { getContributions, getContributionsTotalPercentage } = useContributions(); + const { getAddressName } = useRealm(); + + type Resource = { + amount: number; + resourceId: number; + }; + + const contributions = getContributions(hyperstructureEntityId); + const groupedContributions = contributions.reduce>>((acc, contribution) => { + const { player_address, resource_type, amount } = contribution; + const playerAddressString = player_address.toString(); + if (!acc[playerAddressString]) { + acc[playerAddressString] = {}; + } + if (!acc[playerAddressString][resource_type]) { + acc[playerAddressString][resource_type] = 0n; + } + acc[playerAddressString][resource_type] += amount; + return acc; + }, {}); + + const resourceContributions: Record = Object.entries(groupedContributions).reduce( + (acc, [playerAddress, resources]) => { + acc[playerAddress] = Object.entries(resources).map(([resourceType, amount]) => ({ + amount: Number(amount), + resourceId: Number(resourceType), + })); + return acc; + }, + {} as Record, + ); + + const [showContributions, setShowContributions] = useState(false); + + // Calculate percentages and sort contributors + const sortedContributors = Object.entries(groupedContributions) + .map(([playerAddress, resources]) => ({ + playerAddress, + resources, + percentage: getContributionsTotalPercentage(resourceContributions[playerAddress]) * 100, + })) + .sort((a, b) => b.percentage - a.percentage); + + return ( +
+
setShowContributions(!showContributions)} + > + Contributors + +
+ {showContributions && ( +
+ {sortedContributors.map(({ playerAddress, resources, percentage }) => ( +
+
+
{getAddressName(ContractAddress(playerAddress))}
+
{percentage.toFixed(2)}%
+
+
+ {Object.entries(resources).map(([resourceType, amount]) => ( +
+ + {currencyIntlFormat(divideByPrecision(Number(amount)))} +
+ ))} +
+
+ ))} +
+ )} +
+ ); +}; diff --git a/client/src/ui/components/hyperstructures/HyperstructurePanel.tsx b/client/src/ui/components/hyperstructures/HyperstructurePanel.tsx index 3c7f3fdb4..c449fc0cc 100644 --- a/client/src/ui/components/hyperstructures/HyperstructurePanel.tsx +++ b/client/src/ui/components/hyperstructures/HyperstructurePanel.tsx @@ -16,6 +16,7 @@ import { MAX_NAME_LENGTH, } from "@bibliothecadao/eternum"; import { useMemo, useState } from "react"; +import { ContributionSummary } from "./ContributionSummary"; import { HyperstructureDetails } from "./HyperstructureDetails"; import { HyperstructureResourceChip } from "./HyperstructureResourceChip"; @@ -41,10 +42,10 @@ export const HyperstructurePanel = ({ entity }: any) => { const structureEntityId = useUIStore((state) => state.structureEntityId); const { useProgress } = useHyperstructures(); - const { getContributionsByPlayerAddress } = useContributions(); + const { useContributionsByPlayerAddress } = useContributions(); const progresses = useProgress(entity.entity_id); - const contributions = getContributionsByPlayerAddress(BigInt(account.address), entity.entity_id); + const myContributions = useContributionsByPlayerAddress(BigInt(account.address), entity.entity_id); const updates = useUpdates(entity.entity_id); @@ -93,26 +94,29 @@ export const HyperstructurePanel = ({ entity }: any) => { /> ); }); - }, [progresses, contributions]); + }, [progresses, myContributions]); const initialPoints = useMemo(() => { - return calculateCompletionPoints(contributions); - }, [contributions, updates]); + return calculateCompletionPoints(myContributions); + }, [myContributions, updates]); - const shares = useMemo(() => { - return LeaderboardManager.instance().getShares(ContractAddress(account.address), entity.entity_id); - }, [contributions, updates]); + const myShares = useMemo(() => { + return LeaderboardManager.instance().getAddressShares(ContractAddress(account.address), entity.entity_id); + }, [myContributions, updates]); return ( -
-
-
Owner: {ownerName}
-
+
+
+
+
Owner:
+
{ownerName}
+
+
{editName ? (
setNaming(name)} maxLength={MAX_NAME_LENGTH} @@ -138,48 +142,48 @@ export const HyperstructurePanel = ({ entity }: any) => {
) : ( -

{entity.name}

- )} - - {account.address === entity.owner && ( - <> - - +
+
{entity.name}
+ {account.address === entity.owner && ( + + )} +
)}
-
-
-
-
Initial points received
-
{currencyIntlFormat(initialPoints)}
+
+
+
+
Initial points
+
{currencyIntlFormat(initialPoints)}
-
-
-
Progress
-
{currencyIntlFormat(progresses.percentage)}%
+
+
+
Progress
+
{currencyIntlFormat(progresses.percentage)}%
-
-
-
Shares
-
{currencyIntlFormat((shares || 0) * 100)}%
+
+
+
Shares
+
{currencyIntlFormat((myShares || 0) * 100)}%
-
-
-
Points/cycle
-
- {currencyIntlFormat((shares || 0) * HYPERSTRUCTURE_POINTS_PER_CYCLE)} +
+
+
Points/cycle
+
+ {currencyIntlFormat((myShares || 0) * HYPERSTRUCTURE_POINTS_PER_CYCLE)}
+ {progresses.percentage === 100 ? ( ) : ( diff --git a/client/src/ui/components/hyperstructures/HyperstructureResourceChip.tsx b/client/src/ui/components/hyperstructures/HyperstructureResourceChip.tsx index 9b1fc7179..b585f55c0 100644 --- a/client/src/ui/components/hyperstructures/HyperstructureResourceChip.tsx +++ b/client/src/ui/components/hyperstructures/HyperstructureResourceChip.tsx @@ -55,10 +55,9 @@ export const HyperstructureResourceChip = ({ }, [resetContributions]); return ( - //
-
+
0 @@ -86,12 +85,12 @@ export const HyperstructureResourceChip = ({ isLabor={false} withTooltip={false} resource={findResourceById(getIconResourceId(resourceId, false))?.trait as string} - size="sm" - className="mr-3 self-center" + size="xs" + className="mr-2 self-center" />
-
{`${progress.percentage}% (${currencyIntlFormat( +
{`${progress.percentage}% (${currencyIntlFormat( progress.amount, )} / ${currencyIntlFormat(progress.costNeeded)})`}
@@ -99,11 +98,14 @@ export const HyperstructureResourceChip = ({ -
setInputValue(maxContributableAmount)}> +
setInputValue(maxContributableAmount)} + > MAX
diff --git a/client/src/ui/components/military/PillageHistory.tsx b/client/src/ui/components/military/PillageHistory.tsx index 4e6d2c558..d01dc27ea 100644 --- a/client/src/ui/components/military/PillageHistory.tsx +++ b/client/src/ui/components/military/PillageHistory.tsx @@ -2,22 +2,13 @@ import { ClientComponents } from "@/dojo/createClientComponents"; import { useDojo } from "@/hooks/context/DojoContext"; import { getEntitiesUtils } from "@/hooks/helpers/useEntities"; import { ResourceCost } from "@/ui/elements/ResourceCost"; -import { divideByPrecision, formatSecondsLeftInDaysHoursMinutes } from "@/ui/utils/utils"; +import { divideByPrecision, formatResources, formatSecondsLeftInDaysHoursMinutes } from "@/ui/utils/utils"; import { BattleSide, ID, Resource } from "@bibliothecadao/eternum"; import { ComponentValue, defineQuery, getComponentValue, HasValue, isComponentUpdate } from "@dojoengine/recs"; import { useEffect, useMemo, useState } from "react"; type PillageEvent = ComponentValue; -const formatResources = (resources: any[]): Resource[] => { - return resources - .map((resource) => ({ - resourceId: Number(resource[0].value), - amount: Number(resource[1].value), - })) - .filter((resource) => resource.amount > 0); -}; - const PillageHistoryItem = ({ addressName, history }: { addressName: string; history: PillageEvent }) => { const isSuccess = history.winner === BattleSide[BattleSide.Attack]; const formattedResources = useMemo(() => formatResources(history.pillaged_resources), [history.pillaged_resources]); diff --git a/client/src/ui/elements/ResourceIcon.tsx b/client/src/ui/elements/ResourceIcon.tsx index e8db33faf..23cb23dcf 100644 --- a/client/src/ui/elements/ResourceIcon.tsx +++ b/client/src/ui/elements/ResourceIcon.tsx @@ -65,7 +65,7 @@ const Components: { [key: string]: Resource } = Object.freeze({ Knight: { component: , name: "Knight" }, Crossbowman: { component: , name: "Crossbowman" }, Paladin: { component: , name: "Paladin" }, - AncientFragment: { component: , name: "Ancient Fragment" }, + Earthenshard: { component: , name: "Ancient Fragment" }, House: { component: , name: "House" }, Silo: { component: , name: "Silo" }, Timeglass: { component: , name: "Timeglass" }, diff --git a/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx b/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx index d3a81cb60..6143ef032 100644 --- a/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx +++ b/client/src/ui/modules/world-structures/WorldStructuresMenu.tsx @@ -141,8 +141,8 @@ const HyperStructureExtraContent = ({
Shares:{" "} {currencyIntlFormat( - (LeaderboardManager.instance().getShares(ContractAddress(account.address), hyperstructureEntityId) || 0) * - 100, + (LeaderboardManager.instance().getAddressShares(ContractAddress(account.address), hyperstructureEntityId) || + 0) * 100, 0, )} % diff --git a/client/src/ui/utils/utils.tsx b/client/src/ui/utils/utils.tsx index b5bad45d0..ddbffd50a 100644 --- a/client/src/ui/utils/utils.tsx +++ b/client/src/ui/utils/utils.tsx @@ -300,3 +300,12 @@ export function formatElapsedTime(seconds: number): string { export function gramToKg(grams: number): number { return Number(grams) / 1000; } + +export const formatResources = (resources: any[]): Resource[] => { + return resources + .map((resource) => ({ + resourceId: Number(resource[0].value), + amount: Number(resource[1].value), + })) + .filter((resource) => resource.amount > 0); +};