Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add gong sound + cta for settling realm and joining battle #2149

Merged
merged 12 commits into from
Nov 28, 2024
Merged
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@

# ignore manifests
**/*/manifests

# ignore eliza folder
**/eliza

Binary file added client/public/sound/events/gong.mp3
Binary file not shown.
28 changes: 3 additions & 25 deletions client/src/assets/icons/twitter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 3 additions & 6 deletions client/src/hooks/helpers/use-settle-realm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const useSettleRealm = () => {
const [tokenId, setTokenId] = useState<number>(0);
const [errorMessage, setErrorMessage] = useState<string | null>(null);

const { getNextRealmIdForOrder, getRealmIdForOrderAfter, getRandomUnsettledRealmId } = useRealm();
const { getRandomUnsettledRealmId } = useRealm();

const { play: playSign } = useUiSounds(soundSelector.sign);

Expand All @@ -29,18 +29,15 @@ export const useSettleRealm = () => {
try {
setIsLoading(true);
setErrorMessage(null); // Reset error message before attempting to settle
const calldata = [];

calldata.push(Number(id));

console.log(calldata);
const calldata = [Number(id)];

await create_multiple_realms({
signer: account,
realm_ids: [...calldata],
});

playSign();
return id;
} catch (error) {
setErrorMessage("Realm already settled. Please try a different Realm.");
edisontim marked this conversation as resolved.
Show resolved Hide resolved
console.error("Error during minting:", error);
Expand Down
3 changes: 2 additions & 1 deletion client/src/hooks/helpers/useEntities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ export const useEntities = () => {

const getPlayerRealms = (filterFn?: (realm: RealmWithPosition) => boolean) => {
return useMemo(() => {
return filterFn ? playerRealms.filter(filterFn) : playerRealms;
const realms = filterFn ? playerRealms.filter(filterFn) : playerRealms;
return realms.sort((a, b) => a.name.localeCompare(b.name));
}, [playerRealms, filterFn]);
};

Expand Down
1 change: 1 addition & 0 deletions client/src/hooks/useUISound.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const soundSelector = {
unitSelected3: "units/army_selected3.mp3",
unitMarching1: "units/marching1.mp3",
unitMarching2: "units/marching2.mp3",
gong: "events/gong.mp3",
};

export const useUiSounds = (selector: string) => {
Expand Down
2 changes: 1 addition & 1 deletion client/src/three/sound/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const playBuildingSound = (buildingType: BuildingType | undefined, hasSou
const soundFile =
buildingType === undefined
? soundSelector.buildCastle
: (buildingSounds[buildingType as BuildingType] ?? soundSelector.buildMine);
: buildingSounds[buildingType as BuildingType] ?? soundSelector.buildMine;

playSound(soundFile, hasSound, volume);
};
11 changes: 6 additions & 5 deletions client/src/ui/components/bank/LiquidityTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ export const LiquidityTable = ({ bankEntityId, entity_id }: LiquidityTableProps)

const filteredResources = Object.entries(RESOURCE_TIERS).flatMap(([tier, resourceIds]) => {
if (tier === "lords") return [];
return resourceIds.filter((resourceId) =>
resources
.find((r) => r.id === resourceId)
?.trait.toLowerCase()
.includes(searchTerm.toLowerCase()),
return resourceIds.filter(
(resourceId) =>
resources
.find((r) => r.id === resourceId)
?.trait.toLowerCase()
.includes(searchTerm.toLowerCase()),
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/ui/elemen
import { NumberInput } from "@/ui/elements/NumberInput";
import { ChevronsUpDown } from "lucide-react";

const SettleRealmComponent = () => {
const SettleRealmComponent = ({ setSettledRealmId }: { setSettledRealmId: (id: number) => void }) => {
const { playerRealms } = useEntities();

const { settleRealm, isLoading, tokenId, setTokenId, errorMessage } = useSettleRealm();
Expand All @@ -31,7 +31,7 @@ const SettleRealmComponent = () => {
</p>
)}

<div className="flex flex-wrap gap-2 my-1 md:my-3">
<div className="flex flex-wrap justify-center gap-2 my-1 md:my-3">
{playerRealms().map((realm) => (
<div className="border border-gold/20 rounded p-2" key={realm.realm_id}>
{realm.name}
Expand All @@ -43,7 +43,10 @@ const SettleRealmComponent = () => {
<Button
variant={"primary"}
onClick={async () => {
await settleRealm();
const realmId = await settleRealm();
if (realmId) {
setSettledRealmId(realmId);
}
edisontim marked this conversation as resolved.
Show resolved Hide resolved
}}
>
{isLoading ? "Loading..." : "Settle Random Realm"}
Expand Down
42 changes: 34 additions & 8 deletions client/src/ui/components/military/PillageHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,47 @@ import { useDojo } from "@/hooks/context/DojoContext";
import { useEntitiesUtils } from "@/hooks/helpers/useEntities";
import { ResourceCost } from "@/ui/elements/ResourceCost";
import TwitterShareButton from "@/ui/elements/TwitterShareButton";
import { divideByPrecision, formatResources, formatTime } from "@/ui/utils/utils";
import { BattleSide, ID, Resource, ResourcesIds } from "@bibliothecadao/eternum";
import { formatSocialText, twitterTemplates } from "@/ui/socials";
import { divideByPrecision, formatNumber, formatResources, formatTime } from "@/ui/utils/utils";
import { BattleSide, ID, Resource, resources } from "@bibliothecadao/eternum";
import { ComponentValue, defineQuery, getComponentValue, HasValue, isComponentUpdate } from "@dojoengine/recs";
import { useEffect, useMemo, useState } from "react";
import { TroopDisplay } from "./TroopChip";

type PillageEvent = ComponentValue<ClientComponents["events"]["BattlePillageData"]["schema"]>;

const PillageHistoryItem = ({ addressName, history }: { addressName: string; history: PillageEvent }) => {
const {
setup: {
account: { account },
},
} = useDojo();

const isSuccess = history.winner === BattleSide[BattleSide.Attack];
const formattedResources = useMemo(() => formatResources(history.pillaged_resources), [history.pillaged_resources]);

const { getPlayerAddressFromEntity, getAddressNameFromEntity } = useEntitiesUtils();

const attackerIsPlayer = useMemo(
() => getPlayerAddressFromEntity(history.pillager_army_entity_id) === BigInt(account.address),
[getPlayerAddressFromEntity, history.pillager_army_entity_id, account.address],
);

const twitterText = useMemo(() => {
if (isSuccess) {
return `I, Ser ${addressName}, have just raided ${formattedResources
.map((resource) => `${divideByPrecision(resource.amount)} ${ResourcesIds[resource.resourceId]}`)
.join(", ")} in @RealmsEternum.💰💰💰\n\nJoin the battle for victory at ${window.location.origin}`;
if (isSuccess && formattedResources.length > 0 && attackerIsPlayer) {
return formatSocialText(twitterTemplates.pillage, {
enemyName: getAddressNameFromEntity(history.pillaged_structure_entity_id) || "Unknown",
addressName,
resources: formattedResources
.map(
(pillagedResource) =>
`${formatNumber(divideByPrecision(pillagedResource.amount), 0)} ${resources.find(
(resource) => resource.id === pillagedResource.resourceId,
)?.trait}`,
)
.join(", "),
url: window.location.origin,
});
}
edisontim marked this conversation as resolved.
Show resolved Hide resolved
}, [isSuccess, addressName]);
return (
Expand All @@ -28,8 +52,10 @@ const PillageHistoryItem = ({ addressName, history }: { addressName: string; his
<div className={`text-base font-semibold uppercase tracking-wider ${isSuccess ? "text-green" : "text-red"}`}>
{isSuccess ? "Successful Raid" : "Failed Raid"}
</div>
{twitterText && <TwitterShareButton text={twitterText} />}
<div className="text-gold/80 font-medium">by: {addressName}</div>
{twitterText && <TwitterShareButton text={twitterText} buttonSize={"xs"} />}
<div className={`text-gold/80 font-medium ${attackerIsPlayer ? "font-bold" : ""}`}>
{attackerIsPlayer ? "You" : `by: ${addressName}`}
</div>
</div>

<div className="grid grid-cols-4 gap-6 mb-4">
Expand Down
32 changes: 27 additions & 5 deletions client/src/ui/elements/TwitterShareButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,40 @@ import Button from "./Button";
interface Props {
text: string;
className?: string;
callToActionText?: string;
buttonSize?: "xs" | "md";
variant?: "primary" | "secondary" | "success" | "red" | "danger" | "default" | "outline" | "opaque";
}

const TwitterShareButton: React.FC<Props> = ({ text, className }) => {
const TwitterShareButton: React.FC<Props> = ({
text,
className,
callToActionText,
buttonSize = "md",
variant = "primary",
}) => {
const [hover, setHover] = React.useState(false);

const tweetUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}`;

const iconSizeClasses = {
xs: "h-3 w-3",
md: "h-4 w-4",
};

return (
<a href={tweetUrl} target="_blank" rel="noopener noreferrer">
<Button className={`flex flex-row gap-0 sm:gap-5 items-center h-4 sm:h-6 + ${className}`}>
<div className="w-6 h-6 flex items-center justify-center">
<TwitterIcon className="h-5 sm:h-7" />
<Button
className={className}
size={buttonSize}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
variant={variant}
>
<div className={`flex items-center justify-center mr-2 ${iconSizeClasses[buttonSize]}`}>
<TwitterIcon className={`${hover ? "animate-pulse" : ""} transition-all duration-300`} />
</div>
Share to Twitter
<span>{callToActionText ? callToActionText : "Share on X"}</span>
</Button>
</a>
);
Expand Down
19 changes: 13 additions & 6 deletions client/src/ui/modules/military/battle-view/Battle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { BattleProgress } from "./BattleProgress";
import { BattleSideView } from "./BattleSideView";
import { LockedResources } from "./LockedResources";
import { TopScreenView } from "./TopScreenView";
import { BattleTwitterShareButton } from "./battle-twitter-share-button";

export const Battle = ({
battleManager,
Expand Down Expand Up @@ -59,12 +60,18 @@ export const Battle = ({
animate="visible"
exit="hidden"
>
<div className="flex justify-center mb-2">
{battleAdjusted && (
<Button variant="opaque" onClick={() => setShowBattleDetails(!showBattleDetails)}>{`${
!showBattleDetails ? "Details" : "Overview"
}`}</Button>
)}
<div className="flex justify-center mb-2 space-x-1 items-center">
<Button variant="opaque" onClick={() => setShowBattleDetails(!showBattleDetails)} className="h-10">{`${
!showBattleDetails ? "Details" : "Overview"
}`}</Button>
<BattleTwitterShareButton
userArmiesInBattle={userArmiesInBattle}
attackerArmies={attackerArmies}
defenderArmies={defenderArmies}
ownArmySide={ownArmySide}
battleAdjusted={battleAdjusted}
structure={structure}
/>
<HintModalButton className={`relative ${battleAdjusted ? "left-3" : ""}`} section={HintSection.Combat} />
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { ClientComponents } from "@/dojo/createClientComponents";
import { ArmyInfo } from "@/hooks/helpers/useArmies";
import { useEntitiesUtils } from "@/hooks/helpers/useEntities";
import { Structure } from "@/hooks/helpers/useStructures";
import TwitterShareButton from "@/ui/elements/TwitterShareButton";
import { formatSocialText, twitterTemplates } from "@/ui/socials";
import { currencyFormat } from "@/ui/utils/utils";
import { BattleSide } from "@bibliothecadao/eternum";
import { ComponentValue } from "@dojoengine/recs";
import { useMemo } from "react";

export const BattleTwitterShareButton = ({
userArmiesInBattle,
attackerArmies,
defenderArmies,
ownArmySide,
battleAdjusted,
structure,
}: {
userArmiesInBattle: ArmyInfo[];
attackerArmies: ArmyInfo[];
defenderArmies: (ArmyInfo | undefined)[];
ownArmySide: string;
battleAdjusted: ComponentValue<ClientComponents["Battle"]["schema"]> | undefined;
structure: Structure | undefined;
}) => {
const userBattleSide = userArmiesInBattle[0]?.battle_side || BattleSide.None;

const { getAddressNameFromEntity } = useEntitiesUtils();

const getLargestArmyName = (armies: (ArmyInfo | undefined)[]) => {
return armies.reduce(
(acc: { name: string; size: bigint }, army: ArmyInfo | undefined) => {
const armySize =
(army?.troops.crossbowman_count ?? 0n) +
(army?.troops.knight_count ?? 0n) +
(army?.troops.paladin_count ?? 0n);
return armySize > acc.size
? { name: getAddressNameFromEntity(army?.entityOwner.entity_id || 0) ?? "mercenaries", size: armySize }
: acc;
},
{ name: "mercenaries", size: 0n },
).name;
};
edisontim marked this conversation as resolved.
Show resolved Hide resolved

const calculateTotalTroops = (armies: (ArmyInfo | undefined)[], adjustedHealth: bigint | undefined) => {
return (
adjustedHealth ??
armies.reduce(
(acc: bigint, army: ArmyInfo | undefined) =>
acc +
(army?.troops.crossbowman_count ?? 0n) +
(army?.troops.knight_count ?? 0n) +
(army?.troops.paladin_count ?? 0n),
0n,
)
);
};

const enemyName = useMemo(() => {
const name = getLargestArmyName(ownArmySide === BattleSide[BattleSide.Attack] ? defenderArmies : attackerArmies);
return name.charAt(0).toUpperCase() + name.slice(1);
}, [defenderArmies, attackerArmies, ownArmySide]);

const totalAttackerTroops = useMemo(
() => Number(calculateTotalTroops(attackerArmies, battleAdjusted?.attack_army_health.current)),
[attackerArmies, battleAdjusted],
);

const totalDefenderTroops = useMemo(
() => Number(calculateTotalTroops(defenderArmies, battleAdjusted?.defence_army_health.current)),
[defenderArmies, battleAdjusted],
);

const generateTwitterText = () => {
const text = structure
? structure.isMine
? twitterTemplates.underSiege
: twitterTemplates.attacking
: twitterTemplates.battling;
return formatSocialText(text, {
enemyName,
attackerTroops: currencyFormat(totalAttackerTroops, 0),
defenderTroops: currencyFormat(totalDefenderTroops, 0),
url: window.location.origin,
});
};

const twitterText = useMemo(generateTwitterText, [enemyName, totalAttackerTroops, totalDefenderTroops, structure]);

return (
userBattleSide !== BattleSide.None &&
battleAdjusted?.duration_left !== 0n && (
<TwitterShareButton
className="h-10"
variant="opaque"
callToActionText="Call to Arms!"
text={twitterText}
buttonSize="md"
/>
)
);
};
edisontim marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading