Skip to content

Commit

Permalink
Merge pull request #2149 from BibliothecaDAO/feat/call-to-action-and-…
Browse files Browse the repository at this point in the history
…gong

feat: add gong sound + cta for settling realm and joining battle
  • Loading branch information
ponderingdemocritus authored Nov 28, 2024
2 parents 17c519e + 37e0b8e commit d051967
Show file tree
Hide file tree
Showing 18 changed files with 301 additions and 95 deletions.
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.");
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);
}
}}
>
{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,
});
}
}, [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;
};

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"
/>
)
);
};
Loading

0 comments on commit d051967

Please sign in to comment.