Skip to content

Commit

Permalink
fix resource arrivals DDOS
Browse files Browse the repository at this point in the history
  • Loading branch information
aymericdelab committed Dec 20, 2024
1 parent 008c026 commit 8e41926
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 58 deletions.
109 changes: 52 additions & 57 deletions client/src/hooks/helpers/use-resource-arrivals.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import { ContractAddress, ID, Position } from "@bibliothecadao/eternum";
import { useEntityQuery } from "@dojoengine/react";
import {
Entity,
Has,
HasValue,
NotValue,
defineQuery,
getComponentValue,
isComponentUpdate,
runQuery,
} from "@dojoengine/recs";
import { Entity, Has, HasValue, NotValue, defineQuery, getComponentValue, isComponentUpdate } from "@dojoengine/recs";
import { getEntityIdFromKeys } from "@dojoengine/utils";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useDojo } from "../context/DojoContext";
import useNextBlockTimestamp from "../useNextBlockTimestamp";

const DONKEY_RESOURCE_TRACKER = 452312848583266388373324160190187140051835877600158453279131187530910662656n;
const LORDS_RESOURCE_TRACKER = 7237005577332262213973186563042994240829374041602535252466099000494570602496n;

export type ArrivalInfo = {
entityId: ID;
recipientEntityId: ID;
Expand All @@ -23,18 +17,22 @@ export type ArrivalInfo = {
isOwner: boolean;
hasResources: boolean;
isHome: boolean;
originOwner: string;
// resources: Resource[];
};

const getCurrentDonkeyWeightMinimum = () => {
return Number(localStorage.getItem("WEIGHT_MINIMUM") || 0) * 1000;
};

const usePlayerArrivals = () => {
const {
account: { account },
setup: {
components: { Position, Owner, EntityOwner, OwnedResourcesTracker, ArrivalTime, Weight, Resource, Structure },
components: { Position, Owner, EntityOwner, OwnedResourcesTracker, ArrivalTime, Weight, Structure },
},
} = useDojo();

const minWeight = getCurrentDonkeyWeightMinimum();

const playerStructures = useEntityQuery([
Has(Structure),
HasValue(Owner, { address: ContractAddress(account.address) }),
Expand All @@ -49,69 +47,60 @@ const usePlayerArrivals = () => {

const [entitiesWithInventory, setEntitiesWithInventory] = useState<ArrivalInfo[]>([]);

const queryFragments = [
Has(Weight),
Has(ArrivalTime),
Has(EntityOwner),
NotValue(OwnedResourcesTracker, { resource_types: 0n }),
];

const getArrivalsWithResourceOnPosition = useCallback((positions: Position[]) => {
const arrivals = positions.flatMap((position) => {
return Array.from(runQuery([HasValue(Position, { x: position.x, y: position.y }), ...queryFragments]));
});
return arrivals;
}, []);
const hasMinWeight = useCallback(
(entity: Entity) => {
const weight = getComponentValue(Weight, entity);
return !!(weight?.value && Number(weight.value) >= minWeight);
},
[minWeight],
);

const createArrivalInfo = useCallback(
(id: Entity): ArrivalInfo | undefined => {
// Get required component values
const position = getComponentValue(Position, id);
if (!position) return undefined;

const arrivalTime = getComponentValue(ArrivalTime, id);
if (!arrivalTime) return undefined;

const ownedResourceTracker = getComponentValue(OwnedResourcesTracker, id);
const entityOwner = getComponentValue(EntityOwner, id);
const ownerEntityId = getEntityIdFromKeys([BigInt(entityOwner?.entity_owner_id || 0)]);
const owner = getComponentValue(Owner, ownerEntityId);

if (owner?.address !== ContractAddress(account.address)) {
return undefined;
}
// Return early if missing required components
if (!position || !arrivalTime) return undefined;

const ownedResourceTracker = getComponentValue(OwnedResourcesTracker, id);
// Check if entity has special resource types that don't need weight check
const hasSpecialResources =
ownedResourceTracker?.resource_types === DONKEY_RESOURCE_TRACKER ||
ownedResourceTracker?.resource_types === LORDS_RESOURCE_TRACKER;

// Determine if entity meets weight requirements
const meetsWeightRequirement = hasSpecialResources || hasMinWeight(id);

// Get owner information
const ownerEntityId = getEntityIdFromKeys([BigInt(entityOwner?.entity_owner_id || 0)]);
const owner = getComponentValue(Owner, ownerEntityId);
const isOwner = owner?.address === ContractAddress(account.address);

const hasResources = !!ownedResourceTracker && ownedResourceTracker.resource_types !== 0n;
// Check if entity has resources
const hasResources =
meetsWeightRequirement && !!ownedResourceTracker && ownedResourceTracker.resource_types !== 0n;

// Find matching player structure at position
const playerStructurePosition = playerStructurePositions.find(
(structurePosition) => structurePosition.x === position.x && structurePosition.y === position.y,
);

const isHome = !!playerStructurePosition;

return {
entityId: position.entity_id,
recipientEntityId: playerStructurePosition?.entityId || 0,
arrivesAt: arrivalTime.arrives_at,
isOwner: true,
isOwner,
position: { x: position.x, y: position.y },
hasResources,
originOwner: owner?.address.toString(),
isHome,
isHome: !!playerStructurePosition,
};
},
[account, playerStructurePositions],
);

// initial load
useEffect(() => {
const arrivals = getArrivalsWithResourceOnPosition(playerStructurePositions)
.map(createArrivalInfo)
.filter((arrival): arrival is ArrivalInfo => arrival !== undefined)
.filter((arrival) => arrival.hasResources);
setEntitiesWithInventory(arrivals);
}, [playerStructurePositions, getArrivalsWithResourceOnPosition, createArrivalInfo]);

const isMine = useCallback(
(entity: Entity) => {
const entityOwner = getComponentValue(EntityOwner, entity);
Expand All @@ -122,12 +111,21 @@ const usePlayerArrivals = () => {
);

useEffect(() => {
const query = defineQuery([Has(Position), ...queryFragments], { runOnInit: false });
const query = defineQuery(
[
Has(Position),
Has(Weight),
Has(ArrivalTime),
Has(EntityOwner),
NotValue(OwnedResourcesTracker, { resource_types: 0n }),
],
{ runOnInit: false },
);

const handleArrivalUpdate = (arrivals: ArrivalInfo[], newArrival: ArrivalInfo | undefined) => {
if (!newArrival) return arrivals;

if (!newArrival.hasResources || !newArrival.isHome) {
if (!newArrival.hasResources || !newArrival.isHome || !newArrival.isOwner) {
return arrivals.filter((arrival) => arrival.entityId !== newArrival.entityId);
}

Expand All @@ -146,10 +144,7 @@ const usePlayerArrivals = () => {
isComponentUpdate(update, ArrivalTime) ||
isComponentUpdate(update, OwnedResourcesTracker)
) {
const isThisMine = isMine(update.entity);

isThisMine &&
setEntitiesWithInventory((arrivals) => handleArrivalUpdate(arrivals, createArrivalInfo(update.entity)));
setEntitiesWithInventory((arrivals) => handleArrivalUpdate(arrivals, createArrivalInfo(update.entity)));
}
});

Expand Down
24 changes: 23 additions & 1 deletion client/src/ui/modules/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Button from "@/ui/elements/Button";
import { Checkbox } from "@/ui/elements/Checkbox";
import { Headline } from "@/ui/elements/Headline";
import { RangeInput } from "@/ui/elements/RangeInput";
import { addressToNumber, displayAddress } from "@/ui/utils/utils";
import { addressToNumber, currencyIntlFormat, displayAddress } from "@/ui/utils/utils";
import { ContractAddress } from "@bibliothecadao/eternum";
import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
Expand All @@ -42,6 +42,16 @@ export const SettingsWindow = () => {
const isSoundOn = useUIStore((state) => state.isSoundOn);
const toggleSound = useUIStore((state) => state.toggleSound);

const getCurrentDonkeyWeightMinimum = () => {
return Number(localStorage.getItem("WEIGHT_MINIMUM") || 0);
};

const [donkeyWeightLimit, setDonkeyWeightLimit] = useState(getCurrentDonkeyWeightMinimum());

useEffect(() => {
localStorage.setItem("WEIGHT_MINIMUM", donkeyWeightLimit.toString());
}, [donkeyWeightLimit]);

const { toggleFullScreen, isFullScreen } = useScreenOrientation();
const [fullScreen, setFullScreen] = useState<boolean>(isFullScreen());

Expand Down Expand Up @@ -145,6 +155,18 @@ export const SettingsWindow = () => {

<RangeInput value={musicLevel} fromTitle="Mute" onChange={setMusicLevel} title="Music" />
<RangeInput value={effectsLevel} fromTitle="Mute" onChange={setEffectsLevel} title="Effects" />

<Headline>Donkey Settings</Headline>
<RangeInput
value={donkeyWeightLimit}
min={0}
max={100000}
fromTitle="0"
toTitle={currencyIntlFormat(100000)}
onChange={setDonkeyWeightLimit}
title={`Minimum Weight: ${donkeyWeightLimit} kg`}
/>

<Button onClick={() => setShowSettings(false)} variant="outline" className="text-xxs !py-1 !px-2 mr-auto">
Done
</Button>
Expand Down

0 comments on commit 8e41926

Please sign in to comment.